diff --git a/artisan.md b/artisan.md index 6009fdb38a5..794326a4d6a 100644 --- a/artisan.md +++ b/artisan.md @@ -86,7 +86,7 @@ php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider" #### 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`, `optimize`, and `up` commands. If you would like to allow more commands you may add them to the `commands` array in your `tinker.php` configuration file: +Tinker utilizes an "allow" list to determine which Artisan commands are allowed to be run within its shell. By default, you may run the `clear-compiled`, `down`, `env`, `inspire`, `migrate`, `migrate:install`, `up`, and `optimize` commands. If you would like to allow more commands you may add them to the `commands` array in your `tinker.php` configuration file: 'commands' => [ // App\Console\Commands\ExampleCommand::class, @@ -158,6 +158,19 @@ Let's take a look at an example command. Note that we are able to request any de > [!NOTE] > For greater code reuse, it is good practice to keep your console commands light and let them defer to application services to accomplish their tasks. In the example above, note that we inject a service class to do the "heavy lifting" of sending the e-mails. + +#### 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: + + $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`: + + $this->fail('Something went wrong.'); + ### Closure Commands @@ -636,7 +649,7 @@ Sometimes, you may need more manual control over how a progress bar is advanced. $bar->finish(); -> [!NOTE] +> [!NOTE] > For more advanced options, check out the [Symfony Progress Bar component documentation](https://symfony.com/doc/7.0/components/console/helpers/progressbar.html). @@ -656,7 +669,7 @@ If necessary, you may also manually register commands by providing the command's SendEmails::class, ]) - When Artisan boots, all the commands in your application will be resolved by the [service container](/docs/{{version}}/container) and registered with Artisan. +When Artisan boots, all the commands in your application will be resolved by the [service container](/docs/{{version}}/container) and registered with Artisan. ## Programmatically Executing Commands diff --git a/authentication.md b/authentication.md index dcdbff7f4d0..a16e00ac148 100644 --- a/authentication.md +++ b/authentication.md @@ -53,7 +53,9 @@ Want to get started fast? Install a [Laravel application starter kit](/docs/{{ve ### Database Considerations -By default, Laravel includes an `App\Models\User` [Eloquent model](/docs/{{version}}/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. +By default, Laravel includes an `App\Models\User` [Eloquent model](/docs/{{version}}/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. @@ -182,7 +184,7 @@ To determine if the user making the incoming HTTP request is authenticated, you ### Protecting Routes -[Route middleware](/docs/{{version}}/middleware) can be used to only allow authenticated users to access a given route. Laravel ships with an `auth` middleware, which is a [middleware alias](/docs/{{version}}/middleware#middleware-alias) for the `Illuminate\Auth\Middleware\Authenticate` class. Since this middleware is already aliased internally by Laravel, all you need to do is attach the middleware to a route definition: +[Route middleware](/docs/{{version}}/middleware) can be used to only allow authenticated users to access a given route. Laravel ships with an `auth` middleware, which is a [middleware alias](/docs/{{version}}/middleware#middleware-aliases) for the `Illuminate\Auth\Middleware\Authenticate` class. Since this middleware is already aliased internally by Laravel, all you need to do is attach the middleware to a route definition: Route::get('/flights', function () { // Only authenticated users may access this route... @@ -280,8 +282,8 @@ For complex query conditions, you may provide a closure in your array of credent use Illuminate\Database\Eloquent\Builder; if (Auth::attempt([ - 'email' => $email, - 'password' => $password, + 'email' => $email, + 'password' => $password, fn (Builder $query) => $query->has('activeSubscription'), ])) { // Authentication was successful... @@ -360,9 +362,9 @@ To authenticate a user using their database record's primary key, you may use th Auth::loginUsingId(1); -You may pass a boolean value as the second argument to 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: +You may pass a boolean value to the `remember` argument of the `loginUsingId` method. This value indicates if "remember me" functionality is desired for the authenticated session. Remember, this means that the session will be authenticated indefinitely or until the user manually logs out of the application: - Auth::loginUsingId(1, $remember = true); + Auth::loginUsingId(1, remember: true); #### Authenticate a User Once @@ -458,7 +460,7 @@ In addition to calling the `logout` method, it is recommended that you invalidat 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/{{version}}/middleware#middleware-alias): +Before getting started, you should make sure that the `Illuminate\Session\Middleware\AuthenticateSession` middleware is included on the routes that should receive session authentication. Typically, you should place this middleware on a route group definition so that it can be applied to the majority of your application's routes. By default, the `AuthenticateSession` middleware may be attached to a route using the `auth.session` [middleware alias](/docs/{{version}}/middleware#middleware-aliases): Route::middleware(['auth', 'auth.session'])->group(function () { Route::get('/', function () { @@ -741,17 +743,21 @@ Once the configuration file has been published, you may set the `rehash_on_login Laravel dispatches a variety of [events](/docs/{{version}}/events) during the authentication process. You may [define listeners](/docs/{{version}}/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` | +
+ +| 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/authorization.md b/authorization.md index 6c1c79b202b..ec79b888e2f 100644 --- a/authorization.md +++ b/authorization.md @@ -22,6 +22,7 @@ - [Via Middleware](#via-middleware) - [Via Blade Templates](#via-blade-templates) - [Supplying Additional Context](#supplying-additional-context) +- [Authorization & Inertia](#authorization-and-inertia) ## Introduction @@ -187,7 +188,7 @@ When using the `Gate::authorize` method, which throws an `AuthorizationException // 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: @@ -240,7 +241,7 @@ You may use the `after` method to define a closure to be executed after all othe } }); -Similar to the `before` method, if the `after` closure returns a non-null result that result will be considered the result of the authorization check. +Values returned by `after` closures will not override the result of the authorization check unless the gate or policy returned `null`. ### Inline Authorization @@ -383,7 +384,7 @@ When using the `Gate::authorize` method, which throws an `AuthorizationException // 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: @@ -606,7 +607,7 @@ As previously discussed, some policy methods like `create` do not require a mode ### Via Middleware -Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the `Illuminate\Auth\Middleware\Authorize` middleware may be attached to a route using the `can` [middleware alias](/docs/{{version}}/middleware#middleware-alias), which is automatically registered by Laravel. Let's explore an example of using the `can` middleware to authorize that a user can update a post: +Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the `Illuminate\Auth\Middleware\Authorize` middleware may be attached to a route using the `can` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is automatically registered by Laravel. Let's explore an example of using the `can` middleware to authorize that a user can update a post: use App\Models\Post; @@ -728,3 +729,45 @@ When attempting to determine if the authenticated user can update a given 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/{{version}}/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: + +```php + + */ + 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/billing.md b/billing.md index f29dafaf9f0..294f6827856 100644 --- a/billing.md +++ b/billing.md @@ -35,7 +35,7 @@ - [Subscription Quantity](#subscription-quantity) - [Subscriptions With Multiple Products](#subscriptions-with-multiple-products) - [Multiple Subscriptions](#multiple-subscriptions) - - [Metered Billing](#metered-billing) + - [Usage Based Billing](#usage-based-billing) - [Subscription Taxes](#subscription-taxes) - [Subscription Anchor Date](#subscription-anchor-date) - [Canceling Subscriptions](#cancelling-subscriptions) @@ -200,9 +200,6 @@ Once tax calculation has been enabled, any new subscriptions and any one-off inv 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](#syncing-customer-data-with-stripe) and [Tax ID](#tax-ids) methods offered by Cashier to accomplish this. -> [!WARNING] -> No tax is calculated for [single charges](#single-charges) or [single charge checkouts](#single-charge-checkouts). - ### Logging @@ -254,7 +251,7 @@ Offering product and subscription billing via your application can be intimidati To charge customers for non-recurring, single-charge products, we'll utilize Cashier to direct customers to Stripe Checkout, where they will provide their payment details and confirm their purchase. Once the payment has been made via Checkout, the customer will be redirected to a success URL of your choosing within your application: use Illuminate\Http\Request; - + Route::get('/checkout', function (Request $request) { $stripePriceId = 'price_deluxe_album'; @@ -266,8 +263,8 @@ To charge customers for non-recurring, single-charge products, we'll utilize Cas ]); })->name('checkout'); - Route::view('checkout.success')->name('checkout-success'); - Route::view('checkout.cancel')->name('checkout-cancel'); + Route::view('/checkout/success', 'checkout.success')->name('checkout-success'); + Route::view('/checkout/cancel', 'checkout.cancel')->name('checkout-cancel'); As you can see in the example above, we will utilize Cashier's provided `checkout` method to redirect the customer to Stripe Checkout for a given "price identifier". When using Stripe, "prices" refer to [defined prices for specific products](https://stripe.com/docs/products-prices/how-products-and-prices-work). @@ -279,11 +276,11 @@ If necessary, the `checkout` method will automatically create a customer in Stri 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: - + 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, @@ -343,7 +340,7 @@ To learn how to sell subscriptions using Cashier and Stripe Checkout, let's cons First, let's discover how a customer can subscribe to our services. Of course, you can imagine the customer might click a "subscribe" button for the Basic plan on our application's pricing page. This button or link should direct the user to a Laravel route which creates the Stripe Checkout session for their chosen plan: use Illuminate\Http\Request; - + Route::get('/subscription-checkout', function (Request $request) { return $request->user() ->newSubscription('default', 'price_basic_monthly') @@ -966,7 +963,7 @@ The `subscribed` method also makes a great candidate for a [route middleware](/d { if ($request->user() && ! $request->user()->subscribed('default')) { // This user is not a paying customer... - return redirect('billing'); + return redirect('/billing'); } return $next($request); @@ -1312,12 +1309,12 @@ Of course, you may also cancel the subscription entirely: $user->subscription('swimming')->cancel(); - -### Metered Billing + +### Usage Based Billing -[Metered 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. +[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 metered billing, you will first need to create a new product in your Stripe dashboard with a metered price. Then, use the `meteredPrice` to add the metered price ID to a customer subscription: +To start using usage billing, you will first need to create a new product in your Stripe dashboard with a [usage based billing model](https://docs.stripe.com/billing/subscriptions/usage-based/implementation-guide) and a [meter](https://docs.stripe.com/billing/subscriptions/usage-based/recording-usage#configure-meter). After creating the meter, store the associated event name and meter ID, which you will need to report and retrieve usage. Then, use the `meteredPrice` method to add the metered price ID to a customer subscription: use Illuminate\Http\Request; @@ -1343,54 +1340,33 @@ You may also start a metered subscription via [Stripe 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 increment the usage of a metered subscription, you may use the `reportUsage` method: +As your customer uses your application, you will report their usage to Stripe so that they can be billed accurately. To report the usage of a metered event, you may use the `reportMeterEvent` method on your `Billable` model: $user = User::find(1); - $user->subscription('default')->reportUsage(); + $user->reportMeterEvent('emails-sent'); By default, a "usage quantity" of 1 is added to the billing period. Alternatively, you may pass a specific amount of "usage" to add to the customer's usage for the billing period: $user = User::find(1); - $user->subscription('default')->reportUsage(15); - -If your application offers multiple prices on a single subscription, you will need to use the `reportUsageFor` method to specify the metered price you want to report usage for: - - $user = User::find(1); - - $user->subscription('default')->reportUsageFor('price_metered', 15); + $user->reportMeterEvent('emails-sent', quantity: 15); -Sometimes, you may need to update usage which you have previously reported. To accomplish this, you may pass a timestamp or a `DateTimeInterface` instance as the second parameter to `reportUsage`. When doing so, Stripe will update the usage that was reported at that given time. You can continue to update previous usage records as the given date and time is still within the current billing period: +To retrieve a customer's event summary for a meter, you may use a `Billable` instance's `meterEventSummaries` method: $user = User::find(1); - $user->subscription('default')->reportUsage(5, $timestamp); - - -#### Retrieving Usage Records + $meterUsage = $user->meterEventSummaries($meterId); -To retrieve a customer's past usage, you may use a subscription instance's `usageRecords` method: - - $user = User::find(1); + $meterUsage->first()->aggregated_value // 10 - $usageRecords = $user->subscription('default')->usageRecords(); +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. -If your application offers multiple prices on a single subscription, you may use the `usageRecordsFor` method to specify the metered price that you wish to retrieve usage records for: +To [list all meters](https://docs.stripe.com/api/billing/meter/list), you may use a `Billable` instance's `meters` method: $user = User::find(1); - $usageRecords = $user->subscription('default')->usageRecordsFor('price_metered'); - -The `usageRecords` and `usageRecordsFor` methods return a Collection instance containing an associative array of usage records. You may iterate over this array to display a customer's total usage: - - @foreach ($usageRecords as $usageRecord) - - Period Starting: {{ $usageRecord['period']['start'] }} - - Period Ending: {{ $usageRecord['period']['end'] }} - - Total Usage: {{ $usageRecord['total_usage'] }} - @endforeach - -For a full reference of all usage data returned and how to use Stripe's cursor based pagination, please consult [the official Stripe API documentation](https://stripe.com/docs/api/usage_records/subscription_item_summary_list). + $user->meters(); ### Subscription Taxes @@ -2218,7 +2194,7 @@ Some payment methods require additional data in order to confirm payments. For e $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. diff --git a/blade.md b/blade.md index 44c5db32223..7b08cc4825c 100644 --- a/blade.md +++ b/blade.md @@ -18,6 +18,7 @@ - [Comments](#comments) - [Components](#components) - [Rendering Components](#rendering-components) + - [Index Components](#index-components) - [Passing Data to Components](#passing-data-to-components) - [Component Attributes](#component-attributes) - [Reserved Keywords](#reserved-keywords) @@ -423,8 +424,10 @@ If you are in a nested loop, you may access the parent loop's `$loop` variable v 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. | @@ -436,6 +439,8 @@ The `$loop` variable also contains a variety of other useful properties: | `$loop->depth` | The nesting level of the current loop. | | `$loop->parent` | When in a nested loop, the parent's loop variable. | +
+ ### Conditional Classes & Styles @@ -478,10 +483,12 @@ Likewise, the `@style` directive may be used to conditionally add inline CSS sty 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`: ```blade -active)) /> +active)) +/> ``` Likewise, the `@selected` directive may be used to indicate if a given select option should be "selected": @@ -505,19 +512,23 @@ Additionally, the `@disabled` directive may be used to indicate if a given eleme Moreover, the `@readonly` directive may be used to indicate if a given element should be "readonly": ```blade -isNotAdmin()) /> +isNotAdmin()) +/> ``` In addition, the `@required` directive may be used to indicate if a given element should be "required": ```blade -isAdmin()) /> +isAdmin()) +/> ``` @@ -746,6 +757,26 @@ If you would like to conditionally render your component, you may define a `shou 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: + +```none +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: + +```blade + + ... + ... + +``` + ### Passing Data to Components @@ -865,7 +896,7 @@ You may execute this method from your component template by invoking the variabl #### 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. The closure will receive a `$data` array as its only argument. This array will contain several elements that provide information about the component: +Blade components also allow you to access the component name, attributes, and slot inside the class's render method. However, in order to access this data, you should return a closure from your component's `render` method: use Closure; @@ -874,15 +905,24 @@ Blade components also allow you to access the component name, attributes, and sl */ public function render(): Closure { - return function (array $data) { - // $data['componentName']; - // $data['attributes']; - // $data['slot']; - - return '
Components content
'; + return function () { + return '
Components content
'; }; } +The closure returned by your component's `render` method may also receive a `$data` array as its only argument. This array will contain several elements that provide information about the component: + + return function (array $data) { + // $data['componentName']; + // $data['attributes']; + // $data['slot']; + + return '
Components content
'; + } + +> [!WARNING] +> The elements in the `$data` array should never be directly embedded into the Blade string returned by your `render` method, as doing so could allow remote code execution via malicious attribute content. + 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. @@ -1355,10 +1395,10 @@ This directory structure allows you to render the accordion component and its it 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 an `index.blade.php` file within a component's template directory. When an `index.blade.php` template exists for the component, it will be rendered as the "root" node of the component. So, we can continue to use the same Blade syntax given in the example above; however, we will adjust our directory structure like so: +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: ```none -/resources/views/components/accordion/index.blade.php +/resources/views/components/accordion/accordion.blade.php /resources/views/components/accordion/item.blade.php ``` @@ -1422,7 +1462,7 @@ Because the `color` prop was only passed into the parent (``), it won't ``` > [!WARNING] -> The `@aware` directive can not 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 can not be accessed by the `@aware` directive. +> 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 @@ -1493,7 +1533,7 @@ Once the `layout` component has been defined, we may create a Blade view that ut @foreach ($tasks as $task) - {{ $task }} +
{{ $task }}
@endforeach
``` @@ -1509,7 +1549,7 @@ Remember, content that is injected into a component will be supplied to the defa @foreach ($tasks as $task) - {{ $task }} +
{{ $task }}
@endforeach ``` @@ -1628,9 +1668,11 @@ The `@error` directive may be used to quickly check if [validation error message - + class="@error('title') is-invalid @enderror" +/> @error('title')
{{ $message }}
@@ -1644,9 +1686,11 @@ Since the `@error` directive compiles to an "if" statement, you may use the `@el - + class="@error('email') is-invalid @else is-valid @enderror" +/> ``` You may pass [the name of a specific error bag](/docs/{{version}}/validation#named-error-bags) as the second parameter to the `@error` directive to retrieve validation error messages on pages containing multiple forms: @@ -1656,9 +1700,11 @@ You may pass [the name of a specific error bag](/docs/{{version}}/validation#nam - + class="@error('email', 'login') is-invalid @enderror" +/> @error('email', 'login')
{{ $message }}
diff --git a/broadcasting.md b/broadcasting.md index 0f3611264c1..1c0ef6aae8d 100644 --- a/broadcasting.md +++ b/broadcasting.md @@ -91,10 +91,10 @@ Before broadcasting any events, you should first configure and run a [queue work ### Reverb -When running the `install:broadcasting` command, you will be prompted to install [Laravel Reverb](/docs/{{version}}/reverb). Of course, you may also install Reverb manually using the Composer package manager. Since Reverb is currently in beta, you will need to explicitly install the beta release: +When running the `install:broadcasting` command, you will be prompted to install [Laravel Reverb](/docs/{{version}}/reverb). Of course, you may also install Reverb manually using the Composer package manager. ```sh -composer require laravel/reverb:@beta +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: @@ -361,7 +361,7 @@ When a user is viewing one of their orders, we don't want them to have to refres /** * The order instance. * - * @var \App\Order + * @var \App\Models\Order */ public $order; } @@ -672,10 +672,7 @@ Finally, you may place the authorization logic for your channel in the channel c /** * Create a new channel instance. */ - public function __construct() - { - // ... - } + public function __construct() {} /** * Authenticate the user's access to the channel. @@ -724,7 +721,7 @@ However, remember that we also broadcast the task's creation. If your JavaScript #### 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/mzabriskie/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. +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: diff --git a/cache.md b/cache.md index 169e6b63711..289641e6cfd 100644 --- a/cache.md +++ b/cache.md @@ -90,6 +90,8 @@ Before using the [DynamoDB](https://aws.amazon.com/dynamodb) cache driver, you m 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: ```shell @@ -109,6 +111,13 @@ In addition, you should ensure that values are provided for the DynamoDB cache s ], ``` + +#### 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 @@ -202,6 +211,19 @@ You may use the `rememberForever` method to retrieve an item from the cache or s 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/{{version}}/helpers#deferred-functions) is registered to refresh the cached value after the response is sent to the user. If a request is made after the second value, the cache is considered expired, and the value is recalculated immediately, which may result in a slower response for the user: + + $value = Cache::flexible('users', [5, 10], function () { + return DB::table('users')->get(); + }); + #### Retrieve and Delete @@ -209,6 +231,8 @@ If you need to retrieve an item from the cache and then delete the item, you may $value = Cache::pull('key'); + $value = Cache::pull('key', 'default'); + ### Storing Items in the Cache @@ -310,7 +334,7 @@ The `get` method also accepts a closure. After the closure is executed, Laravel // 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 can not be acquired within the specified time limit, an `Illuminate\Contracts\Cache\LockTimeoutException` will be thrown: +If the lock is not available at the moment you request it, you may instruct Laravel to wait for a specified number of seconds. If the lock cannot be acquired within the specified time limit, an `Illuminate\Contracts\Cache\LockTimeoutException` will be thrown: use Illuminate\Contracts\Cache\LockTimeoutException; @@ -323,7 +347,7 @@ If the lock is not available at the moment you request it, you may instruct Lara } catch (LockTimeoutException $e) { // Unable to acquire lock... } finally { - $lock?->release(); + $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: @@ -438,12 +462,16 @@ Once your extension is registered, update the `CACHE_STORE` environment variable To execute code on every cache operation, you may listen for various [events](/docs/{{version}}/events) dispatched by the cache: -Event Name | -------------- | -`Illuminate\Cache\Events\CacheHit` | -`Illuminate\Cache\Events\CacheMissed` | -`Illuminate\Cache\Events\KeyForgotten` | -`Illuminate\Cache\Events\KeyWritten` | +
+ +| Event Name | +| --- | +| `Illuminate\Cache\Events\CacheHit` | +| `Illuminate\Cache\Events\CacheMissed` | +| `Illuminate\Cache\Events\KeyForgotten` | +| `Illuminate\Cache\Events\KeyWritten` | + +
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: diff --git a/cashier-paddle.md b/cashier-paddle.md index e3e8201ce88..23126a4e7e1 100644 --- a/cashier-paddle.md +++ b/cashier-paddle.md @@ -160,7 +160,7 @@ Paddle relies on its own JavaScript library to initiate the Paddle checkout widg ### 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) class to set the currency locale: +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: ```ini CASHIER_CURRENCY_LOCALE=nl_BE @@ -201,7 +201,7 @@ After defining your model, you may instruct Cashier to use your custom model via ### Selling Products -> [!NOTE] +> [!NOTE] > Before utilizing Paddle Checkout, you should define Products with fixed prices in your Paddle dashboard. In addition, you should [configure Paddle's webhook handling](#handling-paddle-webhooks). Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and [Paddle's Checkout Overlay](https://www.paddle.com/billing/checkout), you can easily build modern, robust payment integrations. @@ -235,11 +235,11 @@ In the `buy` view, we will include a button to display the Checkout Overlay. The 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: - + 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, @@ -276,8 +276,8 @@ In this example, the `CompleteOrder` listener might look like the following: namespace App\Listeners; use App\Models\Order; - use Laravel\Cashier\Cashier; - use Laravel\Cashier\Events\TransactionCompleted; + use Laravel\Paddle\Cashier; + use Laravel\Paddle\Events\TransactionCompleted; class CompleteOrder { @@ -444,7 +444,7 @@ Cashier includes a `paddle-button` [Blade component](/docs/{{version}}/blade#com 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: ```html - + Subscribe ``` @@ -579,7 +579,7 @@ The currency will be determined based on the IP address of the request; however, use Laravel\Paddle\Cashier; - $prices = Cashier::productPrices(['pri_123', 'pri_456'], ['address' => [ + $prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [ 'country_code' => 'BE', 'postal_code' => '1234', ]]); @@ -599,7 +599,7 @@ You may also display the subtotal price and tax amount separately: ```blade
    @foreach ($prices as $price) -
  • {{ $price->product_title }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)
  • +
  • {{ $price->product['name'] }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)
  • @endforeach
``` @@ -669,7 +669,7 @@ These defaults will be used for every action in Cashier that generates a [checko You can retrieve a customer by their Paddle Customer ID using the `Cashier::findBillable` method. This method will return an instance of the billable model: - use Laravel\Cashier\Cashier; + use Laravel\Paddle\Cashier; $user = Cashier::findBillable($customerId); @@ -703,7 +703,7 @@ To create a subscription, first retrieve an instance of your billable model from 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 meta data regarding the subscription using the `customData` method: +You may also provide an array of custom metadata regarding the subscription using the `customData` method: $checkout = $request->user()->subscribe($premium = 12345, 'default') ->customData(['key' => 'value']) @@ -722,7 +722,7 @@ After the user has finished their checkout, a `subscription_created` webhook wil ### 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 an valid subscription, even if the subscription is currently within its trial period: +Once a user is subscribed to your application, you may check their subscription status using a variety of convenient methods. First, the `subscribed` method returns `true` if the user has a valid subscription, even if the subscription is currently within its trial period: if ($user->subscribed()) { // ... @@ -755,7 +755,7 @@ The `subscribed` method also makes a great candidate for a [route middleware](/d { if ($request->user() && ! $request->user()->subscribed()) { // This user is not a paying customer... - return redirect('billing'); + return redirect('/billing'); } return $next($request); @@ -1170,7 +1170,7 @@ You may use the `onGenericTrial` method if you wish to know specifically that th You can extend an existing trial period on a subscription by invoking the `extendTrial` method and specifying the moment in time that the trial should end: - $user->subsription()->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: @@ -1220,7 +1220,7 @@ Cashier automatically handles subscription cancelation on failed charges and oth - `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/{{version}}/events#defining-listeners) that will handle the event: +Both events contain the full payload of the Paddle webhook. For example, if you wish to handle the `transaction.billed` webhook, you may register a [listener](/docs/{{version}}/events#defining-listeners) that will handle the event: payload['alert_name'] === 'transaction_billed') { + if ($event->payload['event_type'] === 'transaction.billed') { // Handle the incoming event... } } @@ -1372,7 +1372,7 @@ When listing the transactions for a customer, you may use the transaction instan The `download-invoice` route may look like the following: use Illuminate\Http\Request; - use Laravel\Cashier\Transaction; + use Laravel\Paddle\Transaction; Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) { return $transaction->redirectToInvoicePdf(); diff --git a/collections.md b/collections.md index fc9c1cb5fb1..836cbe4d89b 100644 --- a/collections.md +++ b/collections.md @@ -31,7 +31,7 @@ As mentioned above, the `collect` helper returns a new `Illuminate\Support\Colle $collection = collect([1, 2, 3]); -> [!NOTE] +> [!NOTE] > The results of [Eloquent](/docs/{{version}}/eloquent) queries are always returned as `Collection` instances. @@ -94,9 +94,11 @@ For the majority of the remaining collection documentation, we'll discuss each m
+[after](#method-after) [all](#method-all) [average](#method-average) [avg](#method-avg) +[before](#method-before) [chunk](#method-chunk) [chunkWhile](#method-chunkwhile) [collapse](#method-collapse) @@ -161,6 +163,7 @@ For the majority of the remaining collection documentation, we'll discuss each m [mergeRecursive](#method-mergerecursive) [min](#method-min) [mode](#method-mode) +[multiply](#method-multiply) [nth](#method-nth) [only](#method-only) [pad](#method-pad) @@ -255,8 +258,37 @@ For the majority of the remaining collection documentation, we'll discuss each m } + +#### `after()` {.collection-method .first-collection-method} + +The `after` method returns the item after the given item. `null` is returned if the given item is not found or is the last item: + + $collection = collect([1, 2, 3, 4, 5]); + + $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: + + 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: + + collect([2, 4, 6, 8])->after(function (int $item, int $key) { + return $item > 5; + }); + + // 8 + -#### `all()` {.collection-method .first-collection-method} +#### `all()` {.collection-method} The `all` method returns the underlying array represented by the collection: @@ -287,6 +319,31 @@ The `avg` method returns the [average value](https://en.wikipedia.org/wiki/Avera // 2 + +#### `before()` {.collection-method} + +The `before` method is the opposite of the [`after`](#method-after) method. It returns the item before the given item. `null` is returned if the given item is not found or is the first item: + + $collection = collect([1, 2, 3, 4, 5]); + + $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()` {.collection-method} @@ -375,7 +432,7 @@ The `collect` method is primarily useful for converting [lazy collections](#lazy // [1, 2, 3] -> [!NOTE] +> [!NOTE] > The `collect` method is especially useful when you have an instance of `Enumerable` and need a non-lazy collection instance. Since `collect()` is part of the `Enumerable` contract, you can safely use it to get a `Collection` instance. @@ -468,7 +525,7 @@ The `containsOneItem` method determines whether the collection contains a single This method has the same signature as the [`contains`](#method-contains) method; however, all values are compared using "strict" comparisons. -> [!NOTE] +> [!NOTE] > This method's behavior is modified when using [Eloquent Collections](/docs/{{version}}/eloquent-collections#method-contains). @@ -579,7 +636,7 @@ The `diff` method compares the collection against another collection or a plain // [1, 3, 5] -> [!NOTE] +> [!NOTE] > This method's behavior is modified when using [Eloquent Collections](/docs/{{version}}/eloquent-collections#method-diff). @@ -799,7 +856,7 @@ Primitive types such as `string`, `int`, `float`, `bool`, and `array` may also b return $collection->ensure('int'); -> [!WARNING] +> [!WARNING] > The `ensure` method does not guarantee that elements of different types will not be added to the collection at a later time. @@ -838,7 +895,7 @@ The `except` method returns all items in the collection except for those with th For the inverse of `except`, see the [only](#method-only) method. -> [!NOTE] +> [!NOTE] > This method's behavior is modified when using [Eloquent Collections](/docs/{{version}}/eloquent-collections#method-except). @@ -1015,14 +1072,18 @@ The `forget` method removes an item from the collection by its key: $collection = collect(['name' => 'taylor', 'framework' => 'laravel']); + // Forget a single key... $collection->forget('name'); - $collection->all(); - // ['framework' => 'laravel'] -> [!WARNING] -> Unlike most other collection methods, `forget` does not return a new modified collection; it modifies the collection it is called on. + // Forget multiple keys... + $collection->forget(['name', 'framework']); + + // [] + +> [!WARNING] +> Unlike most other collection methods, `forget` does not return a new modified collection; it modifies and returns the collection it is called on. #### `forPage()` {.collection-method} @@ -1224,7 +1285,7 @@ The `intersect` method removes any values from the original collection that are // [0 => 'Desk', 2 => 'Chair'] -> [!NOTE] +> [!NOTE] > This method's behavior is modified when using [Eloquent Collections](/docs/{{version}}/eloquent-collections#method-intersect). @@ -1413,7 +1474,7 @@ The `map` method iterates through the collection and passes each value to the gi // [2, 4, 6, 8, 10] -> [!WARNING] +> [!WARNING] > Like most other collection methods, `map` returns a new collection instance; it does not modify the collection it is called on. If you want to transform the original collection, use the [`transform`](#method-transform) method. @@ -1427,7 +1488,7 @@ The `mapInto()` method iterates over the collection, creating a new instance of * Create a new currency instance. */ function __construct( - public string $code + public string $code, ) {} } @@ -1633,6 +1694,29 @@ The `mode` method returns the [mode value](https://en.wikipedia.org/wiki/Mode_(s // [1, 2] + +#### `multiply()` {.collection-method} + +The `multiply` method creates the specified number of copies of all items in the collection: + +```php +$users = collect([ + ['name' => 'User #1', 'email' => 'user1@example.com'], + ['name' => 'User #2', 'email' => 'user2@example.com'], +])->multiply(3); + +/* + [ + ['name' => 'User #1', 'email' => 'user1@example.com'], + ['name' => 'User #2', 'email' => 'user2@example.com'], + ['name' => 'User #1', 'email' => 'user1@example.com'], + ['name' => 'User #2', 'email' => 'user2@example.com'], + ['name' => 'User #1', 'email' => 'user1@example.com'], + ['name' => 'User #2', 'email' => 'user2@example.com'], + ] +*/ +``` + #### `nth()` {.collection-method} @@ -1670,7 +1754,7 @@ The `only` method returns the items in the collection with the specified keys: For the inverse of `only`, see the [except](#method-except) method. -> [!NOTE] +> [!NOTE] > This method's behavior is modified when using [Eloquent Collections](/docs/{{version}}/eloquent-collections#method-only). @@ -1758,7 +1842,7 @@ The `pipeInto` method creates a new instance of the given class and passes the c * Create a new ResourceCollection instance. */ public function __construct( - public Collection $collection, + public Collection $collection, ) {} } @@ -2131,7 +2215,7 @@ The `search` method searches the collection for the given value and returns its The search is done using a "loose" comparison, meaning a string with an integer value will be considered equal to an integer of the same value. To use "strict" comparison, pass `true` as the second argument to the method: - collect([2, 4, 6, 8])->search('4', $strict = true); + collect([2, 4, 6, 8])->search('4', strict: true); // false @@ -2220,7 +2304,7 @@ The `skip` method returns a new collection, with the given number of elements re #### `skipUntil()` {.collection-method} -The `skipUntil` method skips over items from the collection until the given callback returns `true` and then returns the remaining items in the collection as a new collection instance: +The `skipUntil` method skips over items from the collection while the given callback returns `false`. Once the callback returns `true` all of the remaining items in the collection will be returned as a new collection: $collection = collect([1, 2, 3, 4]); @@ -2242,13 +2326,13 @@ You may also pass a simple value to the `skipUntil` method to skip all items unt // [3, 4] -> [!WARNING] +> [!WARNING] > If the given value is not found or the callback never returns `true`, the `skipUntil` method will return an empty collection. #### `skipWhile()` {.collection-method} -The `skipWhile` method skips over items from the collection while the given callback returns `true` and then returns the remaining items in the collection as a new collection: +The `skipWhile` method skips over items from the collection while the given callback returns `true`. Once the callback returns `false` all of the remaining items in the collection will be returned as a new collection: $collection = collect([1, 2, 3, 4]); @@ -2260,7 +2344,7 @@ The `skipWhile` method skips over items from the collection while the given call // [4] -> [!WARNING] +> [!WARNING] > If the callback never returns `false`, the `skipWhile` method will return an empty collection. @@ -2369,7 +2453,7 @@ The `sort` method sorts the collection. The sorted collection keeps the original 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. -> [!NOTE] +> [!NOTE] > If you need to sort a collection of nested arrays or objects, see the [`sortBy`](#method-sortby) and [`sortByDesc`](#method-sortbydesc) methods. @@ -2713,7 +2797,7 @@ You may also pass a simple value to the `takeUntil` method to get the items unti // [1, 2] -> [!WARNING] +> [!WARNING] > If the given value is not found or the callback never returns `true`, the `takeUntil` method will return all items in the collection. @@ -2731,7 +2815,7 @@ The `takeWhile` method returns items in the collection until the given callback // [1, 2] -> [!WARNING] +> [!WARNING] > If the callback never returns `false`, the `takeWhile` method will return all items in the collection. @@ -2776,7 +2860,7 @@ The `toArray` method converts the collection into a plain PHP `array`. If the co ] */ -> [!WARNING] +> [!WARNING] > `toArray` also converts all of the collection's nested objects that are an instance of `Arrayable` to an array. If you want to get the raw array underlying the collection, use the [`all`](#method-all) method instead. @@ -2805,7 +2889,7 @@ The `transform` method iterates over the collection and calls the given callback // [2, 4, 6, 8, 10] -> [!WARNING] +> [!WARNING] > Unlike most other collection methods, `transform` modifies the collection itself. If you wish to create a new collection instead, use the [`map`](#method-map) method. @@ -2909,7 +2993,7 @@ Finally, you may also pass your own closure to the `unique` method to specify wh The `unique` method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the [`uniqueStrict`](#method-uniquestrict) method to filter using "strict" comparisons. -> [!NOTE] +> [!NOTE] > This method's behavior is modified when using [Eloquent Collections](/docs/{{version}}/eloquent-collections#method-unique). @@ -3354,7 +3438,6 @@ The `whereNull` method returns items from the collection where the given key is ] */ - #### `wrap()` {.collection-method} @@ -3418,7 +3501,7 @@ Likewise, we can use the `sum` higher order message to gather the total number o ### Introduction -> [!WARNING] +> [!WARNING] > 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. @@ -3609,7 +3692,7 @@ Almost all methods available on the `Collection` class are also available on the
-> [!WARNING] +> [!WARNING] > Methods that mutate the collection (such as `shift`, `pop`, `prepend` etc.) are **not** available on the `LazyCollection` class. diff --git a/concurrency.md b/concurrency.md new file mode 100644 index 00000000000..a5f93276548 --- /dev/null +++ b/concurrency.md @@ -0,0 +1,93 @@ +# Concurrency + +- [Introduction](#introduction) +- [Running Concurrent Tasks](#running-concurrent-tasks) +- [Deferring Concurrent Tasks](#deferring-concurrent-tasks) + + +## Introduction + +> [!WARNING] +> Laravel's `Concurrency` facade is currently in beta while we gather community feedback. + +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. + + +#### Concurrency Compatibility + +If you upgraded to Laravel 11.x from a Laravel 10.x application, you may need to add the `ConcurrencyServiceProvider` to the `providers` array in your application's `config/app.php` configuration file: + +```php +'providers' => ServiceProvider::defaultProviders()->merge([ + /* + * Package Service Providers... + */ + Illuminate\Concurrency\ConcurrencyServiceProvider::class, // [tl! add] + + /* + * Application Service Providers... + */ + App\Providers\AppServiceProvider::class, + App\Providers\AuthServiceProvider::class, + // App\Providers\BroadcastServiceProvider::class, + App\Providers\EventServiceProvider::class, + App\Providers\RouteServiceProvider::class, +])->toArray(), +``` + + +#### 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: + +```bash +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: + +```php +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: + +```php +$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: + +```bash +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: + +```php +use App\Services\Metrics; +use Illuminate\Support\Facades\Concurrency; + +Concurrency::defer([ + fn () => Metrics::report('users'), + fn () => Metrics::report('orders'), +]); +``` diff --git a/configuration.md b/configuration.md index cc6a81a35fd..eed3d3cf03e 100644 --- a/configuration.md +++ b/configuration.md @@ -51,7 +51,7 @@ Laravel's default `.env` file contains some common configuration values that may 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. -> [!NOTE] +> [!NOTE] > Any variable in your `.env` file can be overridden by external environment variables such as server-level or system-level environment variables. @@ -71,8 +71,10 @@ Before loading your application's environment variables, Laravel determines if a 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 | @@ -82,6 +84,8 @@ All variables in your `.env` files are typically parsed as strings, so some rese | 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: ```ini diff --git a/console-tests.md b/console-tests.md index b949302012a..826a7a9f938 100644 --- a/console-tests.md +++ b/console-tests.md @@ -86,6 +86,36 @@ public function test_console_command(): void } ``` +If you are utilizing the `search` or `multisearch` functions provided by [Laravel Prompts](/docs/{{version}}/prompts), you may use the `expectsSearch` assertion to mock the user's input, search results, and selection: + +```php tab=Pest +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); +}); +``` + +```php tab=PHPUnit +/** + * 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: ```php tab=Pest @@ -108,7 +138,7 @@ public function test_console_command(): void } ``` - The `expectsOutputToContain` and `doesntExpectOutputToContain` methods may be used to make assertions against a portion of the output: +The `expectsOutputToContain` and `doesntExpectOutputToContain` methods may be used to make assertions against a portion of the output: ```php tab=Pest test('console command', function () { diff --git a/container.md b/container.md index fbe5fa2c496..a538174bab5 100644 --- a/container.md +++ b/container.md @@ -7,6 +7,7 @@ - [Binding Basics](#binding-basics) - [Binding Interfaces to Implementations](#binding-interfaces-to-implementations) - [Contextual Binding](#contextual-binding) + - [Contextual Attributes](#contextual-attributes) - [Binding Primitives](#binding-primitives) - [Binding Typed Variadics](#binding-typed-variadics) - [Tagging](#tagging) @@ -16,6 +17,7 @@ - [Automatic Injection](#automatic-injection) - [Method Invocation and Injection](#method-invocation-and-injection) - [Container Events](#container-events) + - [Rebinding](#rebinding) - [PSR-11](#psr-11) @@ -29,32 +31,30 @@ Let's look at a simple example: namespace App\Http\Controllers; - use App\Http\Controllers\Controller; - use App\Repositories\UserRepository; - use App\Models\User; + use App\Services\AppleMusic; use Illuminate\View\View; - class UserController extends Controller + class PodcastController extends Controller { /** * Create a new controller instance. */ public function __construct( - protected UserRepository $users, + protected AppleMusic $apple, ) {} /** - * Show the profile for the given user. + * Show information about the given podcast. */ public function show(string $id): View { - $user = $this->users->find($id); - - return view('user.profile', ['user' => $user]); + return view('podcasts.show', [ + 'podcast' => $this->apple->findPodcast($id) + ]); } } -In this example, the `UserController` needs to retrieve users from a data source. So, we will **inject** a service that is able to retrieve users. In this context, our `UserRepository` most likely uses [Eloquent](/docs/{{version}}/eloquent) to retrieve user information from the database. However, since the repository is injected, we are able to easily swap it out with another implementation. We are also able to easily "mock", or create a dummy implementation of the `UserRepository` when testing our application. +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. @@ -171,6 +171,12 @@ The `scoped` method binds a class or interface into the container that should on return new Transistor($app->make(PodcastParser::class)); }); +You may use the `scopedIf` method to register a scoped container binding only if a binding has not already been registered for the given type: + + $this->app->scopedIf(Transistor::class, function (Application $app) { + return new Transistor($app->make(PodcastParser::class)); + }); + #### Binding Instances @@ -201,7 +207,7 @@ This statement tells the container that it should inject the `RedisEventPusher` * Create a new class instance. */ public function __construct( - protected EventPusher $pusher + protected EventPusher $pusher, ) {} @@ -227,13 +233,121 @@ Sometimes you may have two classes that utilize the same interface, but you wish 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/{{version}}/filesystem): + +```php +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: + + make('config')->get($attribute->key, $attribute->default); + } + } + ### Binding Primitives Sometimes you may have a class that receives some injected classes, but also needs an injected primitive value such as an integer. You may easily use contextual binding to inject any value your class may need: use App\Http\Controllers\UserController; - + $this->app->when(UserController::class) ->needs('$variableName') ->give($value); @@ -382,7 +496,7 @@ If you would like to have the Laravel container instance itself injected into a * Create a new class instance. */ public function __construct( - protected Container $container + protected Container $container, ) {} @@ -390,32 +504,29 @@ If you would like to have the Laravel container instance itself injected into a Alternatively, and importantly, you may type-hint the dependency in the constructor of a class that is resolved by the container, including [controllers](/docs/{{version}}/controllers), [event listeners](/docs/{{version}}/events), [middleware](/docs/{{version}}/middleware), and more. Additionally, you may type-hint dependencies in the `handle` method of [queued jobs](/docs/{{version}}/queues). In practice, this is how most of your objects should be resolved by the container. -For example, you may type-hint a repository defined by your application in a controller's constructor. The repository will automatically be resolved and injected into the class: +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: users->findOrFail($id); - - return $user; + return $this->apple->findPodcast($id); } } @@ -428,14 +539,14 @@ Sometimes you may wish to invoke a method on an object instance while allowing t namespace App; - use App\Repositories\UserRepository; + use App\Services\AppleMusic; - class UserReport + class PodcastStats { /** - * Generate a new user report. + * Generate a new podcast stats report. */ - public function generate(UserRepository $repository): array + public function generate(AppleMusic $apple): array { return [ // ... @@ -445,17 +556,17 @@ Sometimes you may wish to invoke a method on an object instance while allowing t You may invoke the `generate` method via the container like so: - use App\UserReport; + use App\PodcastStats; use Illuminate\Support\Facades\App; - $report = App::call([new UserReport, 'generate']); + $stats = App::call([new PodcastStats, 'generate']); The `call` method accepts any PHP callable. The container's `call` method may even be used to invoke a closure while automatically injecting its dependencies: - use App\Repositories\UserRepository; + use App\Services\AppleMusic; use Illuminate\Support\Facades\App; - $result = App::call(function (UserRepository $repository) { + $result = App::call(function (AppleMusic $apple) { // ... }); @@ -477,6 +588,28 @@ The service container fires an event each time it resolves an object. You may li 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: + + 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 diff --git a/context.md b/context.md index 18a6a4569ce..99eeaf19fad 100644 --- a/context.md +++ b/context.md @@ -76,7 +76,7 @@ When the job is dispatched, any information currently stored in the context is c ```php class ProcessPodcast implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Queueable; // ... @@ -155,7 +155,7 @@ Context::when( ### Stacks -Context offers the ability to create "stacks", which are lists of data stored in the order that they where added. You can add information to a stack by invoking the `push` method: +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: ```php use Illuminate\Support\Facades\Context; @@ -183,6 +183,29 @@ DB::listen(function ($event) { }); ``` +You may determine if a value is in a stack using the `stackContains` and `hiddenStackContains` methods: + +```php +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: + +```php +use Illuminate\Support\Facades\Context; +use Illuminate\Support\Str; + +return Context::stackContains('breadcrumbs', function ($value) { + return Str::startsWith($value, 'query_'); +}); +``` + ## Retrieving Context @@ -206,6 +229,18 @@ The `pull` method may be used to retrieve information from the context and immed $value = Context::pull('key'); ``` +If context data is stored in a [stack](#stacks), you may pop items from the stack using the `pop` method: + +```php +Context::push('breadcrumbs', 'first_value', 'second_value'); + +Context::pop('breadcrumbs') +// second_value + +Context::get('breadcrumbs'); +// ['first_value'] +``` + If you would like to retrieve all of the information stored in the context, you may invoke the `all` method: ```php @@ -282,6 +317,7 @@ Context::addHiddenIf(/* ... */); Context::pushHidden(/* ... */); Context::getHidden(/* ... */); Context::pullHidden(/* ... */); +Context::popHidden(/* ... */); Context::onlyHidden(/* ... */); Context::allHidden(/* ... */); Context::hasHidden(/* ... */); @@ -318,7 +354,7 @@ public function boot(): void } ``` -> [!NOTE] +> [!NOTE] > 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. @@ -346,5 +382,5 @@ public function boot(): void } ``` -> [!NOTE] +> [!NOTE] > 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/contracts.md b/contracts.md index de852b6a7bb..f8c1884f74a 100644 --- a/contracts.md +++ b/contracts.md @@ -71,84 +71,87 @@ When the event listener is resolved, the service container will read the type-hi This table provides a quick reference to all of the Laravel contracts and their equivalent facades: -| Contract | References Facade | -|--------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------| -| [Illuminate\Contracts\Auth\Access\Authorizable](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Access/Authorizable.php) |    | -| [Illuminate\Contracts\Auth\Access\Gate](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Access/Gate.php) | `Gate` | -| [Illuminate\Contracts\Auth\Authenticatable](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Authenticatable.php) |    | -| [Illuminate\Contracts\Auth\CanResetPassword](https://github.com/illuminate/contracts/blob/{{version}}/Auth/CanResetPassword.php) |   | -| [Illuminate\Contracts\Auth\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Factory.php) | `Auth` | -| [Illuminate\Contracts\Auth\Guard](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Guard.php) | `Auth::guard()` | -| [Illuminate\Contracts\Auth\PasswordBroker](https://github.com/illuminate/contracts/blob/{{version}}/Auth/PasswordBroker.php) | `Password::broker()` | -| [Illuminate\Contracts\Auth\PasswordBrokerFactory](https://github.com/illuminate/contracts/blob/{{version}}/Auth/PasswordBrokerFactory.php) | `Password` | -| [Illuminate\Contracts\Auth\StatefulGuard](https://github.com/illuminate/contracts/blob/{{version}}/Auth/StatefulGuard.php) |   | -| [Illuminate\Contracts\Auth\SupportsBasicAuth](https://github.com/illuminate/contracts/blob/{{version}}/Auth/SupportsBasicAuth.php) |   | -| [Illuminate\Contracts\Auth\UserProvider](https://github.com/illuminate/contracts/blob/{{version}}/Auth/UserProvider.php) |   | -| [Illuminate\Contracts\Bus\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Bus/Dispatcher.php) | `Bus` | -| [Illuminate\Contracts\Bus\QueueingDispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Bus/QueueingDispatcher.php) | `Bus::dispatchToQueue()` | -| [Illuminate\Contracts\Broadcasting\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/Factory.php) | `Broadcast` | -| [Illuminate\Contracts\Broadcasting\Broadcaster](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/Broadcaster.php) | `Broadcast::connection()` | -| [Illuminate\Contracts\Broadcasting\ShouldBroadcast](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/ShouldBroadcast.php) |   | -| [Illuminate\Contracts\Broadcasting\ShouldBroadcastNow](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/ShouldBroadcastNow.php) |   | -| [Illuminate\Contracts\Cache\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Factory.php) | `Cache` | -| [Illuminate\Contracts\Cache\Lock](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Lock.php) |   | -| [Illuminate\Contracts\Cache\LockProvider](https://github.com/illuminate/contracts/blob/{{version}}/Cache/LockProvider.php) |   | -| [Illuminate\Contracts\Cache\Repository](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Repository.php) | `Cache::driver()` | -| [Illuminate\Contracts\Cache\Store](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Store.php) |   | -| [Illuminate\Contracts\Config\Repository](https://github.com/illuminate/contracts/blob/{{version}}/Config/Repository.php) | `Config` | -| [Illuminate\Contracts\Console\Application](https://github.com/illuminate/contracts/blob/{{version}}/Console/Application.php) |   | -| [Illuminate\Contracts\Console\Kernel](https://github.com/illuminate/contracts/blob/{{version}}/Console/Kernel.php) | `Artisan` | -| [Illuminate\Contracts\Container\Container](https://github.com/illuminate/contracts/blob/{{version}}/Container/Container.php) | `App` | -| [Illuminate\Contracts\Cookie\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Cookie/Factory.php) | `Cookie` | -| [Illuminate\Contracts\Cookie\QueueingFactory](https://github.com/illuminate/contracts/blob/{{version}}/Cookie/QueueingFactory.php) | `Cookie::queue()` | -| [Illuminate\Contracts\Database\ModelIdentifier](https://github.com/illuminate/contracts/blob/{{version}}/Database/ModelIdentifier.php) |   | -| [Illuminate\Contracts\Debug\ExceptionHandler](https://github.com/illuminate/contracts/blob/{{version}}/Debug/ExceptionHandler.php) |   | -| [Illuminate\Contracts\Encryption\Encrypter](https://github.com/illuminate/contracts/blob/{{version}}/Encryption/Encrypter.php) | `Crypt` | -| [Illuminate\Contracts\Events\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Events/Dispatcher.php) | `Event` | -| [Illuminate\Contracts\Filesystem\Cloud](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Cloud.php) | `Storage::cloud()` | -| [Illuminate\Contracts\Filesystem\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Factory.php) | `Storage` | -| [Illuminate\Contracts\Filesystem\Filesystem](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Filesystem.php) | `Storage::disk()` | -| [Illuminate\Contracts\Foundation\Application](https://github.com/illuminate/contracts/blob/{{version}}/Foundation/Application.php) | `App` | -| [Illuminate\Contracts\Hashing\Hasher](https://github.com/illuminate/contracts/blob/{{version}}/Hashing/Hasher.php) | `Hash` | -| [Illuminate\Contracts\Http\Kernel](https://github.com/illuminate/contracts/blob/{{version}}/Http/Kernel.php) |   | -| [Illuminate\Contracts\Mail\MailQueue](https://github.com/illuminate/contracts/blob/{{version}}/Mail/MailQueue.php) | `Mail::queue()` | -| [Illuminate\Contracts\Mail\Mailable](https://github.com/illuminate/contracts/blob/{{version}}/Mail/Mailable.php) |   | -| [Illuminate\Contracts\Mail\Mailer](https://github.com/illuminate/contracts/blob/{{version}}/Mail/Mailer.php) | `Mail` | -| [Illuminate\Contracts\Notifications\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Notifications/Dispatcher.php) | `Notification` | -| [Illuminate\Contracts\Notifications\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Notifications/Factory.php) | `Notification` | -| [Illuminate\Contracts\Pagination\LengthAwarePaginator](https://github.com/illuminate/contracts/blob/{{version}}/Pagination/LengthAwarePaginator.php) |   | -| [Illuminate\Contracts\Pagination\Paginator](https://github.com/illuminate/contracts/blob/{{version}}/Pagination/Paginator.php) |   | -| [Illuminate\Contracts\Pipeline\Hub](https://github.com/illuminate/contracts/blob/{{version}}/Pipeline/Hub.php) |   | -| [Illuminate\Contracts\Pipeline\Pipeline](https://github.com/illuminate/contracts/blob/{{version}}/Pipeline/Pipeline.php) | `Pipeline`; | -| [Illuminate\Contracts\Queue\EntityResolver](https://github.com/illuminate/contracts/blob/{{version}}/Queue/EntityResolver.php) |   | -| [Illuminate\Contracts\Queue\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Factory.php) | `Queue` | -| [Illuminate\Contracts\Queue\Job](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Job.php) |   | -| [Illuminate\Contracts\Queue\Monitor](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Monitor.php) | `Queue` | -| [Illuminate\Contracts\Queue\Queue](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Queue.php) | `Queue::connection()` | -| [Illuminate\Contracts\Queue\QueueableCollection](https://github.com/illuminate/contracts/blob/{{version}}/Queue/QueueableCollection.php) |   | -| [Illuminate\Contracts\Queue\QueueableEntity](https://github.com/illuminate/contracts/blob/{{version}}/Queue/QueueableEntity.php) |   | -| [Illuminate\Contracts\Queue\ShouldQueue](https://github.com/illuminate/contracts/blob/{{version}}/Queue/ShouldQueue.php) |   | -| [Illuminate\Contracts\Redis\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Redis/Factory.php) | `Redis` | -| [Illuminate\Contracts\Routing\BindingRegistrar](https://github.com/illuminate/contracts/blob/{{version}}/Routing/BindingRegistrar.php) | `Route` | -| [Illuminate\Contracts\Routing\Registrar](https://github.com/illuminate/contracts/blob/{{version}}/Routing/Registrar.php) | `Route` | -| [Illuminate\Contracts\Routing\ResponseFactory](https://github.com/illuminate/contracts/blob/{{version}}/Routing/ResponseFactory.php) | `Response` | -| [Illuminate\Contracts\Routing\UrlGenerator](https://github.com/illuminate/contracts/blob/{{version}}/Routing/UrlGenerator.php) | `URL` | -| [Illuminate\Contracts\Routing\UrlRoutable](https://github.com/illuminate/contracts/blob/{{version}}/Routing/UrlRoutable.php) |   | -| [Illuminate\Contracts\Session\Session](https://github.com/illuminate/contracts/blob/{{version}}/Session/Session.php) | `Session::driver()` | -| [Illuminate\Contracts\Support\Arrayable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Arrayable.php) |   | -| [Illuminate\Contracts\Support\Htmlable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Htmlable.php) |   | -| [Illuminate\Contracts\Support\Jsonable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Jsonable.php) |   | -| [Illuminate\Contracts\Support\MessageBag](https://github.com/illuminate/contracts/blob/{{version}}/Support/MessageBag.php) |   | -| [Illuminate\Contracts\Support\MessageProvider](https://github.com/illuminate/contracts/blob/{{version}}/Support/MessageProvider.php) |   | -| [Illuminate\Contracts\Support\Renderable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Renderable.php) |   | -| [Illuminate\Contracts\Support\Responsable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Responsable.php) |   | -| [Illuminate\Contracts\Translation\Loader](https://github.com/illuminate/contracts/blob/{{version}}/Translation/Loader.php) |   | -| [Illuminate\Contracts\Translation\Translator](https://github.com/illuminate/contracts/blob/{{version}}/Translation/Translator.php) | `Lang` | -| [Illuminate\Contracts\Validation\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Factory.php) | `Validator` | -| [Illuminate\Contracts\Validation\ImplicitRule](https://github.com/illuminate/contracts/blob/{{version}}/Validation/ImplicitRule.php) |   | -| [Illuminate\Contracts\Validation\Rule](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Rule.php) |   | -| [Illuminate\Contracts\Validation\ValidatesWhenResolved](https://github.com/illuminate/contracts/blob/{{version}}/Validation/ValidatesWhenResolved.php) |   | -| [Illuminate\Contracts\Validation\Validator](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Validator.php) | `Validator::make()` | -| [Illuminate\Contracts\View\Engine](https://github.com/illuminate/contracts/blob/{{version}}/View/Engine.php) |   | -| [Illuminate\Contracts\View\Factory](https://github.com/illuminate/contracts/blob/{{version}}/View/Factory.php) | `View` | -| [Illuminate\Contracts\View\View](https://github.com/illuminate/contracts/blob/{{version}}/View/View.php) | `View::make()` | +
+ +| Contract | References Facade | +| --- | --- | +| [Illuminate\Contracts\Auth\Access\Authorizable](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Access/Authorizable.php) |   | +| [Illuminate\Contracts\Auth\Access\Gate](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Access/Gate.php) | `Gate` | +| [Illuminate\Contracts\Auth\Authenticatable](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Authenticatable.php) |   | +| [Illuminate\Contracts\Auth\CanResetPassword](https://github.com/illuminate/contracts/blob/{{version}}/Auth/CanResetPassword.php) |   | +| [Illuminate\Contracts\Auth\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Factory.php) | `Auth` | +| [Illuminate\Contracts\Auth\Guard](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Guard.php) | `Auth::guard()` | +| [Illuminate\Contracts\Auth\PasswordBroker](https://github.com/illuminate/contracts/blob/{{version}}/Auth/PasswordBroker.php) | `Password::broker()` | +| [Illuminate\Contracts\Auth\PasswordBrokerFactory](https://github.com/illuminate/contracts/blob/{{version}}/Auth/PasswordBrokerFactory.php) | `Password` | +| [Illuminate\Contracts\Auth\StatefulGuard](https://github.com/illuminate/contracts/blob/{{version}}/Auth/StatefulGuard.php) |   | +| [Illuminate\Contracts\Auth\SupportsBasicAuth](https://github.com/illuminate/contracts/blob/{{version}}/Auth/SupportsBasicAuth.php) |   | +| [Illuminate\Contracts\Auth\UserProvider](https://github.com/illuminate/contracts/blob/{{version}}/Auth/UserProvider.php) |   | +| [Illuminate\Contracts\Broadcasting\Broadcaster](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/Broadcaster.php) | `Broadcast::connection()` | +| [Illuminate\Contracts\Broadcasting\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/Factory.php) | `Broadcast` | +| [Illuminate\Contracts\Broadcasting\ShouldBroadcast](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/ShouldBroadcast.php) |   | +| [Illuminate\Contracts\Broadcasting\ShouldBroadcastNow](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/ShouldBroadcastNow.php) |   | +| [Illuminate\Contracts\Bus\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Bus/Dispatcher.php) | `Bus` | +| [Illuminate\Contracts\Bus\QueueingDispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Bus/QueueingDispatcher.php) | `Bus::dispatchToQueue()` | +| [Illuminate\Contracts\Cache\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Factory.php) | `Cache` | +| [Illuminate\Contracts\Cache\Lock](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Lock.php) |   | +| [Illuminate\Contracts\Cache\LockProvider](https://github.com/illuminate/contracts/blob/{{version}}/Cache/LockProvider.php) |   | +| [Illuminate\Contracts\Cache\Repository](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Repository.php) | `Cache::driver()` | +| [Illuminate\Contracts\Cache\Store](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Store.php) |   | +| [Illuminate\Contracts\Config\Repository](https://github.com/illuminate/contracts/blob/{{version}}/Config/Repository.php) | `Config` | +| [Illuminate\Contracts\Console\Application](https://github.com/illuminate/contracts/blob/{{version}}/Console/Application.php) |   | +| [Illuminate\Contracts\Console\Kernel](https://github.com/illuminate/contracts/blob/{{version}}/Console/Kernel.php) | `Artisan` | +| [Illuminate\Contracts\Container\Container](https://github.com/illuminate/contracts/blob/{{version}}/Container/Container.php) | `App` | +| [Illuminate\Contracts\Cookie\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Cookie/Factory.php) | `Cookie` | +| [Illuminate\Contracts\Cookie\QueueingFactory](https://github.com/illuminate/contracts/blob/{{version}}/Cookie/QueueingFactory.php) | `Cookie::queue()` | +| [Illuminate\Contracts\Database\ModelIdentifier](https://github.com/illuminate/contracts/blob/{{version}}/Database/ModelIdentifier.php) |   | +| [Illuminate\Contracts\Debug\ExceptionHandler](https://github.com/illuminate/contracts/blob/{{version}}/Debug/ExceptionHandler.php) |   | +| [Illuminate\Contracts\Encryption\Encrypter](https://github.com/illuminate/contracts/blob/{{version}}/Encryption/Encrypter.php) | `Crypt` | +| [Illuminate\Contracts\Events\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Events/Dispatcher.php) | `Event` | +| [Illuminate\Contracts\Filesystem\Cloud](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Cloud.php) | `Storage::cloud()` | +| [Illuminate\Contracts\Filesystem\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Factory.php) | `Storage` | +| [Illuminate\Contracts\Filesystem\Filesystem](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Filesystem.php) | `Storage::disk()` | +| [Illuminate\Contracts\Foundation\Application](https://github.com/illuminate/contracts/blob/{{version}}/Foundation/Application.php) | `App` | +| [Illuminate\Contracts\Hashing\Hasher](https://github.com/illuminate/contracts/blob/{{version}}/Hashing/Hasher.php) | `Hash` | +| [Illuminate\Contracts\Http\Kernel](https://github.com/illuminate/contracts/blob/{{version}}/Http/Kernel.php) |   | +| [Illuminate\Contracts\Mail\Mailable](https://github.com/illuminate/contracts/blob/{{version}}/Mail/Mailable.php) |   | +| [Illuminate\Contracts\Mail\Mailer](https://github.com/illuminate/contracts/blob/{{version}}/Mail/Mailer.php) | `Mail` | +| [Illuminate\Contracts\Mail\MailQueue](https://github.com/illuminate/contracts/blob/{{version}}/Mail/MailQueue.php) | `Mail::queue()` | +| [Illuminate\Contracts\Notifications\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Notifications/Dispatcher.php) | `Notification`| +| [Illuminate\Contracts\Notifications\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Notifications/Factory.php) | `Notification` | +| [Illuminate\Contracts\Pagination\LengthAwarePaginator](https://github.com/illuminate/contracts/blob/{{version}}/Pagination/LengthAwarePaginator.php) |   | +| [Illuminate\Contracts\Pagination\Paginator](https://github.com/illuminate/contracts/blob/{{version}}/Pagination/Paginator.php) |   | +| [Illuminate\Contracts\Pipeline\Hub](https://github.com/illuminate/contracts/blob/{{version}}/Pipeline/Hub.php) |   | +| [Illuminate\Contracts\Pipeline\Pipeline](https://github.com/illuminate/contracts/blob/{{version}}/Pipeline/Pipeline.php) | `Pipeline` | +| [Illuminate\Contracts\Queue\EntityResolver](https://github.com/illuminate/contracts/blob/{{version}}/Queue/EntityResolver.php) |   | +| [Illuminate\Contracts\Queue\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Factory.php) | `Queue` | +| [Illuminate\Contracts\Queue\Job](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Job.php) |   | +| [Illuminate\Contracts\Queue\Monitor](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Monitor.php) | `Queue` | +| [Illuminate\Contracts\Queue\Queue](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Queue.php) | `Queue::connection()` | +| [Illuminate\Contracts\Queue\QueueableCollection](https://github.com/illuminate/contracts/blob/{{version}}/Queue/QueueableCollection.php) |   | +| [Illuminate\Contracts\Queue\QueueableEntity](https://github.com/illuminate/contracts/blob/{{version}}/Queue/QueueableEntity.php) |   | +| [Illuminate\Contracts\Queue\ShouldQueue](https://github.com/illuminate/contracts/blob/{{version}}/Queue/ShouldQueue.php) |   | +| [Illuminate\Contracts\Redis\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Redis/Factory.php) | `Redis` | +| [Illuminate\Contracts\Routing\BindingRegistrar](https://github.com/illuminate/contracts/blob/{{version}}/Routing/BindingRegistrar.php) | `Route` | +| [Illuminate\Contracts\Routing\Registrar](https://github.com/illuminate/contracts/blob/{{version}}/Routing/Registrar.php) | `Route` | +| [Illuminate\Contracts\Routing\ResponseFactory](https://github.com/illuminate/contracts/blob/{{version}}/Routing/ResponseFactory.php) | `Response` | +| [Illuminate\Contracts\Routing\UrlGenerator](https://github.com/illuminate/contracts/blob/{{version}}/Routing/UrlGenerator.php) | `URL` | +| [Illuminate\Contracts\Routing\UrlRoutable](https://github.com/illuminate/contracts/blob/{{version}}/Routing/UrlRoutable.php) |   | +| [Illuminate\Contracts\Session\Session](https://github.com/illuminate/contracts/blob/{{version}}/Session/Session.php) | `Session::driver()` | +| [Illuminate\Contracts\Support\Arrayable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Arrayable.php) |   | +| [Illuminate\Contracts\Support\Htmlable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Htmlable.php) |   | +| [Illuminate\Contracts\Support\Jsonable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Jsonable.php) |   | +| [Illuminate\Contracts\Support\MessageBag](https://github.com/illuminate/contracts/blob/{{version}}/Support/MessageBag.php) |   | +| [Illuminate\Contracts\Support\MessageProvider](https://github.com/illuminate/contracts/blob/{{version}}/Support/MessageProvider.php) |   | +| [Illuminate\Contracts\Support\Renderable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Renderable.php) |   | +| [Illuminate\Contracts\Support\Responsable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Responsable.php) |   | +| [Illuminate\Contracts\Translation\Loader](https://github.com/illuminate/contracts/blob/{{version}}/Translation/Loader.php) |   | +| [Illuminate\Contracts\Translation\Translator](https://github.com/illuminate/contracts/blob/{{version}}/Translation/Translator.php) | `Lang` | +| [Illuminate\Contracts\Validation\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Factory.php) | `Validator` | +| [Illuminate\Contracts\Validation\ValidatesWhenResolved](https://github.com/illuminate/contracts/blob/{{version}}/Validation/ValidatesWhenResolved.php) |   | +| [Illuminate\Contracts\Validation\ValidationRule](https://github.com/illuminate/contracts/blob/{{version}}/Validation/ValidationRule.php) |   | +| [Illuminate\Contracts\Validation\Validator](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Validator.php) | `Validator::make()` | +| [Illuminate\Contracts\View\Engine](https://github.com/illuminate/contracts/blob/{{version}}/View/Engine.php) |   | +| [Illuminate\Contracts\View\Factory](https://github.com/illuminate/contracts/blob/{{version}}/View/Factory.php) | `View` | +| [Illuminate\Contracts\View\View](https://github.com/illuminate/contracts/blob/{{version}}/View/View.php) | `View::make()` | + +
diff --git a/contributions.md b/contributions.md index 9d2b7dd050a..ce9668a2c47 100644 --- a/contributions.md +++ b/contributions.md @@ -28,6 +28,7 @@ The Laravel source code is managed on GitHub, and there are repositories for eac - [Laravel Application](https://github.com/laravel/laravel) - [Laravel Art](https://github.com/laravel/art) +- [Laravel Breeze](https://github.com/laravel/breeze) - [Laravel Documentation](https://github.com/laravel/docs) - [Laravel Dusk](https://github.com/laravel/dusk) - [Laravel Cashier Stripe](https://github.com/laravel/cashier) @@ -36,8 +37,7 @@ The Laravel source code is managed on GitHub, and there are repositories for eac - [Laravel Envoy](https://github.com/laravel/envoy) - [Laravel Folio](https://github.com/laravel/folio) - [Laravel Framework](https://github.com/laravel/framework) -- [Laravel Homestead](https://github.com/laravel/homestead) -- [Laravel Homestead Build Scripts](https://github.com/laravel/settler) +- [Laravel Homestead](https://github.com/laravel/homestead) ([Build Scripts](https://github.com/laravel/settler)) - [Laravel Horizon](https://github.com/laravel/horizon) - [Laravel Jetstream](https://github.com/laravel/jetstream) - [Laravel Passport](https://github.com/laravel/passport) @@ -50,7 +50,7 @@ The Laravel source code is managed on GitHub, and there are repositories for eac - [Laravel Scout](https://github.com/laravel/scout) - [Laravel Socialite](https://github.com/laravel/socialite) - [Laravel Telescope](https://github.com/laravel/telescope) -- [Laravel Website](https://github.com/laravel/laravel.com-next) +- [Laravel Website](https://github.com/laravel/laravel.com) @@ -81,7 +81,7 @@ Informal discussion regarding bugs, new features, and implementation of existing ## Which Branch? -**All** bug fixes should be sent to the latest version that supports bug fixes (currently `10.x`). Bug fixes should **never** be sent to the `master` branch unless they fix features that exist only in the upcoming release. +**All** bug fixes should be sent to the latest version that supports bug fixes (currently `11.x`). Bug fixes should **never** be sent to the `master` branch unless they fix features that exist only in the upcoming release. **Minor** features that are **fully backward compatible** with the current release may be sent to the latest stable branch (currently `11.x`). diff --git a/controllers.md b/controllers.md index d85b60ef152..c2c22c7bffa 100644 --- a/controllers.md +++ b/controllers.md @@ -38,7 +38,7 @@ Let's take a look at an example of a basic controller. A controller may have any middleware('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: @@ -181,20 +181,24 @@ You may even register many resource controllers at once by passing an array to t #### 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 +
+ +| 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 can not be found for any of the resource's routes: +Typically, a 404 HTTP response will be generated if an implicitly bound resource model is not found. However, you may customize this behavior by calling the `missing` method when defining your resource route. The `missing` method accepts a closure that will be invoked if an implicitly bound model cannot be found for any of the resource's routes: use App\Http\Controllers\PhotoController; use Illuminate\Http\Request; @@ -305,15 +309,19 @@ Often, it is not entirely necessary to have both the parent and the child IDs wi 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 +
+ +| 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 @@ -337,7 +345,7 @@ By default, `Route::resource` will create the route parameters for your resource 'users' => 'admin_user' ]); - The example above generates the following URI for the resource's `show` route: +The example above generates the following URI for the resource's `show` route: /users/{admin_user} @@ -407,11 +415,15 @@ 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 +
+ +| 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: @@ -419,13 +431,17 @@ Singleton resources may also be nested within a standard resource: Route::singleton('photos.thumbnail', ThumbnailController::class); ``` -In this example, the `photos` resource would receive all of the [standard resource routes](#actions-handled-by-resource-controller); however, the `thumbnail` resource would be a singleton resource with the following routes: +In this example, the `photos` resource would receive all of the [standard resource routes](#actions-handled-by-resource-controllers); 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 | -| 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 @@ -438,8 +454,10 @@ 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 | @@ -447,6 +465,8 @@ In this example, the following routes will be registered. As you can see, a `DEL | 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: ```php diff --git a/csrf.md b/csrf.md index efdabdbe978..fb825fb71b1 100644 --- a/csrf.md +++ b/csrf.md @@ -28,9 +28,9 @@ Without CSRF protection, a malicious website could create an HTML form that poin ``` - 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. +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. +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 @@ -65,7 +65,7 @@ The `Illuminate\Foundation\Http\Middleware\ValidateCsrfToken` [middleware](/docs ### CSRF Tokens & SPAs -If you are building a SPA that is utilizing Laravel as an API backend, you should consult the [Laravel Sanctum documentation](/docs/{{version}}/sanctum) for information on authenticating with your API and protecting against CSRF vulnerabilities. +If you are building an SPA that is utilizing Laravel as an API backend, you should consult the [Laravel Sanctum documentation](/docs/{{version}}/sanctum) for information on authenticating with your API and protecting against CSRF vulnerabilities. ### Excluding URIs From CSRF Protection diff --git a/database-testing.md b/database-testing.md index 5da114a8748..4545a81268e 100644 --- a/database-testing.md +++ b/database-testing.md @@ -196,6 +196,13 @@ Assert that a table in the database contains the given number of records: $this->assertDatabaseCount('users', 5); + +#### assertDatabaseEmpty + +Assert that a table in the database contains no records: + + $this->assertDatabaseEmpty('users'); + #### assertDatabaseHas @@ -220,7 +227,7 @@ Assert that a table in the database does not contain records matching the given The `assertSoftDeleted` method may be used to assert a given Eloquent model has been "soft deleted": $this->assertSoftDeleted($user); - + #### assertNotSoftDeleted diff --git a/database.md b/database.md index dd528b6546b..829eca93572 100644 --- a/database.md +++ b/database.md @@ -22,11 +22,13 @@ Almost every modern web application interacts with a database. Laravel makes int - 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.35.0+ +- 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 @@ -50,7 +52,7 @@ By default, foreign key constraints are enabled for SQLite connections. If you w DB_FOREIGN_KEYS=false ``` -> [!NOTE] +> [!NOTE] > If you use the [Laravel installer](/docs/{{version}}/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/{{version}}/migrations) for you. @@ -103,7 +105,7 @@ To see how read / write connections should be configured, let's look at this exa 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => env('DB_CHARSET', 'utf8mb4'), - 'collation' => env('DB_COLLATION', 'utf8mb4_0900_ai_ci'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, @@ -291,6 +293,7 @@ If you would like to specify a closure that is invoked for each SQL query execut // $query->sql; // $query->bindings; // $query->time; + // $query->toRawSql(); }); } } diff --git a/deployment.md b/deployment.md index 6bbc2c07f18..11ad9a88089 100644 --- a/deployment.md +++ b/deployment.md @@ -4,6 +4,8 @@ - [Server Requirements](#server-requirements) - [Server Configuration](#server-configuration) - [Nginx](#nginx) + - [FrankenPHP](#frankenphp) + - [Directory Permissions](#directory-permissions) - [Optimization](#optimization) - [Caching Configuration](#optimizing-configuration-loading) - [Caching Events](#caching-events) @@ -75,10 +77,11 @@ server { error_page 404 /index.php; - location ~ \.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).* { @@ -87,6 +90,22 @@ server { } ``` + +### 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: + +```shell +frankenphp php-server -r public/ +``` + +To take advantage of more powerful features supported by FrankenPHP, such as its [Laravel Octane](/docs/{{version}}/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 @@ -96,7 +115,7 @@ When deploying your application to production, there are a variety of files that php artisan optimize ``` -The `optimize:clear` method may be used to remove all of the cache files generated by the `optimize` command: +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: ```shell php artisan optimize:clear diff --git a/documentation.md b/documentation.md index 2ea03e6a683..c0eecca09eb 100644 --- a/documentation.md +++ b/documentation.md @@ -34,6 +34,7 @@ - [Broadcasting](/docs/{{version}}/broadcasting) - [Cache](/docs/{{version}}/cache) - [Collections](/docs/{{version}}/collections) + - [Concurrency](/docs/{{version}}/concurrency) - [Context](/docs/{{version}}/context) - [Contracts](/docs/{{version}}/contracts) - [Events](/docs/{{version}}/events) @@ -63,6 +64,7 @@ - [Migrations](/docs/{{version}}/migrations) - [Seeding](/docs/{{version}}/seeding) - [Redis](/docs/{{version}}/redis) + - [MongoDB](/docs/{{version}}/mongodb) - ## Eloquent ORM - [Getting Started](/docs/{{version}}/eloquent) - [Relationships](/docs/{{version}}/eloquent-relationships) diff --git a/dusk.md b/dusk.md index 6987eb7d3c2..0f49217b95a 100644 --- a/dusk.md +++ b/dusk.md @@ -64,7 +64,7 @@ To get started, you should install [Google Chrome](https://www.google.com/chrome composer require laravel/dusk --dev ``` -> [!WARNING] +> [!WARNING] > 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: @@ -75,7 +75,7 @@ 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. -> [!NOTE] +> [!NOTE] > If you are using [Laravel Sail](/docs/{{version}}/sail) to manage your local development environment, please also consult the Sail documentation on [configuring and running Dusk tests](/docs/{{version}}/sail#laravel-dusk). @@ -97,7 +97,7 @@ php artisan dusk:chrome-driver --all php artisan dusk:chrome-driver --detect ``` -> [!WARNING] +> [!WARNING] > 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/`. @@ -181,7 +181,7 @@ class ExampleTest extends DuskTestCase } ``` -> [!WARNING] +> [!WARNING] > SQLite in-memory databases may not be used when executing Dusk tests. Since the browser executes within its own process, it will not be able to access the in-memory databases of other processes. @@ -220,7 +220,7 @@ class ExampleTest extends DuskTestCase By default, this trait will truncate all tables except the `migrations` table. If you would like to customize the tables that should be truncated, you may define a `$tablesToTruncate` property on your test class: -> [!NOTE] +> [!NOTE] > If you are using Pest, you should define properties or methods on the base `DuskTestCase` class or on any class your test file extends. /** @@ -287,7 +287,7 @@ The `dusk` command accepts any argument that is normally accepted by the Pest / php artisan dusk --group=foo ``` -> [!NOTE] +> [!NOTE] > If you are using [Laravel Sail](/docs/{{version}}/sail) to manage your local development environment, please consult the Sail documentation on [configuring and running Dusk tests](/docs/{{version}}/sail#laravel-dusk). @@ -423,7 +423,7 @@ The `visit` method may be used to navigate to a given URI within your applicatio You may use the `visitRoute` method to navigate to a [named route](/docs/{{version}}/routing#named-routes): - $browser->visitRoute('login'); + $browser->visitRoute($routeName, $parameters); You may navigate "back" and "forward" using the `back` and `forward` methods: @@ -506,7 +506,7 @@ Often, you will be testing pages that require authentication. You can use Dusk's ->visit('/home'); }); -> [!WARNING] +> [!WARNING] > After using the `loginAs` method, the user session will be maintained for all tests within the file. @@ -707,7 +707,7 @@ The `attach` method may be used to attach a file to a `file` input element. Like $browser->attach('photo', __DIR__.'/photos/mountains.png'); -> [!WARNING] +> [!WARNING] > The attach function requires the `Zip` PHP extension to be installed and enabled on your server. @@ -738,7 +738,7 @@ You may use the `seeLink` method to determine if a link with the given display t // ... } -> [!WARNING] +> [!WARNING] > These methods interact with jQuery. If jQuery is not available on the page, Dusk will automatically inject it into the page so it is available for the test's duration. @@ -752,7 +752,7 @@ Another valuable use case for the `keys` method is sending a "keyboard shortcut" $browser->keys('.app', ['{command}', 'j']); -> [!NOTE] +> [!NOTE] > All modifier keys such as `{command}` are wrapped in `{}` characters, and match the constants defined in the `Facebook\WebDriver\WebDriverKeys` class, which can be [found on GitHub](https://github.com/php-webdriver/php-webdriver/blob/master/lib/WebDriverKeys.php). @@ -2108,7 +2108,7 @@ class ExampleTest extends DuskTestCase ## Continuous Integration -> [!WARNING] +> [!WARNING] > 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`. @@ -2121,11 +2121,11 @@ To run Dusk tests on [Heroku CI](https://www.heroku.com/continuous-integration), "test": { "buildpacks": [ { "url": "heroku/php" }, - { "url": "https://github.com/heroku/heroku-buildpack-google-chrome" } + { "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 > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /dev/null 2>&1 &' && php artisan dusk" + "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" } } } @@ -2140,7 +2140,7 @@ To run your Dusk tests on [Travis CI](https://travis-ci.org), use the following language: php php: - - 7.3 + - 8.2 addons: chrome: stable @@ -2191,20 +2191,20 @@ jobs: - name: Upgrade Chrome Driver run: php artisan dusk:chrome-driver --detect - name: Start Chrome Driver - run: ./vendor/laravel/dusk/bin/chromedriver-linux & + 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@v2 + uses: actions/upload-artifact@v4 with: name: screenshots path: tests/Browser/screenshots - name: Upload Console Logs if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: console path: tests/Browser/console diff --git a/eloquent-collections.md b/eloquent-collections.md index 10f2192e1c5..860a1f7ea8e 100644 --- a/eloquent-collections.md +++ b/eloquent-collections.md @@ -67,6 +67,7 @@ In addition, the `Illuminate\Database\Eloquent\Collection` class provides a supe [diff](#method-diff) [except](#method-except) [find](#method-find) +[findOrFail](#method-find-or-fail) [fresh](#method-fresh) [intersect](#method-intersect) [load](#method-load) @@ -88,7 +89,7 @@ In addition, the `Illuminate\Database\Eloquent\Collection` class provides a supe The `append` method may be used to indicate that an attribute should be [appended](/docs/{{version}}/eloquent-serialization#appending-values-to-json) for every model in the collection. This method accepts an array of attributes or a single attribute: $users->append('team'); - + $users->append(['team', 'is_admin']); @@ -125,6 +126,15 @@ The `find` method returns the model that has a primary key matching the given ke $user = $users->find(1); + +#### `findOrFail($key)` {.collection-method} + +The `findOrFail` method returns the model that has a primary key matching the given key or throws an `Illuminate\Database\Eloquent\ModelNotFoundException` exception if no matching model can be found in the collection: + + $users = User::all(); + + $user = $users->findOrFail(1); + #### `fresh($with = [])` {.collection-method} @@ -151,7 +161,7 @@ The `load` method eager loads the given relationships for all models in the coll $users->load(['comments', 'posts']); $users->load('comments.author'); - + $users->load(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); @@ -162,7 +172,7 @@ The `loadMissing` method eager loads the given relationships for all models in t $users->loadMissing(['comments', 'posts']); $users->loadMissing('comments.author'); - + $users->loadMissing(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); @@ -232,7 +242,23 @@ The `unique` method returns all of the unique models in the collection. Any mode ## Custom Collections -If you would like to use a custom `Collection` object when interacting with a given model, you may define a `newCollection` method on your model: +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: + + ### Enum Casting diff --git a/eloquent-relationships.md b/eloquent-relationships.md index 088dc480544..f4ed21b32d8 100644 --- a/eloquent-relationships.md +++ b/eloquent-relationships.md @@ -2,8 +2,8 @@ - [Introduction](#introduction) - [Defining Relationships](#defining-relationships) - - [One to One](#one-to-one) - - [One to Many](#one-to-many) + - [One to One / Has One](#one-to-one) + - [One to Many / Has Many](#one-to-many) - [One to Many (Inverse) / Belongs To](#one-to-many-inverse) - [Has One of Many](#has-one-of-many) - [Has One Through](#has-one-through) @@ -68,7 +68,7 @@ Eloquent relationships are defined as methods on your Eloquent model classes. Si But, before diving too deep into using relationships, let's learn how to define each type of relationship supported by Eloquent. -### One to One +### 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: @@ -148,7 +148,7 @@ If the parent model does not use `id` as its primary key, or you wish to find th } -### One to Many +### 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: @@ -194,6 +194,53 @@ Like the `hasOne` method, you may also override the foreign and local keys by pa 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: + +```php +$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: + + 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: + +```php +use App\Models\Post; + +$posts = Post::with([ + 'comments' => fn ($comments) => $comments->chaperone(), +])->get(); +``` + ### One to Many (Inverse) / Belongs To @@ -1008,6 +1055,46 @@ You may also retrieve the parent of a polymorphic child model by accessing the n 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: + +```php +$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: + + 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: + +```php +use App\Models\Post; + +$posts = Post::with([ + 'comments' => fn ($comments) => $comments->chaperone(), +])->get(); +``` + ### One of Many (Polymorphic) @@ -1415,6 +1502,12 @@ You may occasionally need to add query constraints based on the "type" of the re } )->get(); +Sometimes you may want to query for the children of a "morph to" relationship's parent. You may accomplish this using the `whereMorphedTo` and `whereNotMorphedTo` methods, which will automatically determine the proper morph type mapping for the given model. These methods accept the name of the `morphTo` relationship as their first argument and the related parent model as their second argument: + + $comments = Comment::whereMorphedTo('commentable', $post) + ->orWhereMorphedTo('commentable', $video) + ->get(); + #### Querying All Related Models @@ -1941,7 +2034,7 @@ The `createQuietly` and `createManyQuietly` methods may be used to create a mode $user->posts()->createQuietly([ 'title' => 'Post title.', ]); - + $user->posts()->createManyQuietly([ ['title' => 'First post.'], ['title' => 'Second post.'], diff --git a/eloquent-resources.md b/eloquent-resources.md index 2623c7412b5..ea01e2d81bf 100644 --- a/eloquent-resources.md +++ b/eloquent-resources.md @@ -493,7 +493,7 @@ If you would like to customize the information included in the `links` or `meta` return $default; } - + ### Conditional Attributes @@ -675,7 +675,7 @@ If your intermediate table is using an accessor other than `pivot`, you may use ### 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 `link` information when transforming a resource collection: +Some JSON API standards require the addition of meta data to your resource and resource collections responses. This often includes things like `links` to the resource or related resources, or meta data about the resource itself. If you need to return additional meta data about a resource, include it in your `toArray` method. For example, you might include `links` information when transforming a resource collection: /** * Transform the resource into an array. diff --git a/eloquent-serialization.md b/eloquent-serialization.md index 98e1956a5f0..45f4df9cce7 100644 --- a/eloquent-serialization.md +++ b/eloquent-serialization.md @@ -61,7 +61,7 @@ Alternatively, you may cast a model or collection to a string, which will automa Since models and collections are converted to JSON when cast to a string, you can return Eloquent objects directly from your application's routes or controllers. Laravel will automatically serialize your Eloquent models and collections to JSON when they are returned from routes or controllers: - Route::get('users', function () { + Route::get('/users', function () { return User::all(); }); @@ -84,9 +84,9 @@ Sometimes you may wish to limit the attributes, such as passwords, that are incl class User extends Model { /** - * The attributes that should be hidden for arrays. + * The attributes that should be hidden for serialization. * - * @var array + * @var array */ protected $hidden = ['password']; } diff --git a/eloquent.md b/eloquent.md index 0a5cd0d4135..3cdc7f0b0cd 100644 --- a/eloquent.md +++ b/eloquent.md @@ -313,7 +313,7 @@ If you need to customize the names of the columns used to store the timestamps, If you would like to perform model operations without the model having its `updated_at` timestamp modified, you may operate on the model within a closure given to the `withoutTimestamps` method: - Model::withoutTimestamps(fn () => $post->increment(['reads'])); + Model::withoutTimestamps(fn () => $post->increment('reads')); ### Database Connections @@ -388,7 +388,7 @@ Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction()); ## Retrieving Models -Once you have created a model and [its associated database table](/docs/{{version}}/migrations#writing-migrations), you are ready to start retrieving data from your database. You can think of each Eloquent model as a powerful [query builder](/docs/{{version}}/queries) allowing you to fluently query the database table associated with the model. The model's `all` method will retrieve all of the records from the model's associated database table: +Once you have created a model and [its associated database table](/docs/{{version}}/migrations#generating-migrations), you are ready to start retrieving data from your database. You can think of each Eloquent model as a powerful [query builder](/docs/{{version}}/queries) allowing you to fluently query the database table associated with the model. The model's `all` method will retrieve all of the records from the model's associated database table: use App\Models\Flight; @@ -479,7 +479,20 @@ If you are filtering the results of the `chunk` method based on a column that yo Flight::where('departed', true) ->chunkById(200, function (Collection $flights) { $flights->each->update(['departed' => false]); - }, $column = 'id'); + }, column: 'id'); +``` + +Since the `chunkById` and `lazyById` methods add their own "where" conditions to the query being executed, you should typically [logically group](/docs/{{version}}/queries#logical-grouping) your own conditions within a closure: + +```php +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'); ``` @@ -499,7 +512,7 @@ If you are filtering the results of the `lazy` method based on a column that you ```php Flight::where('departed', true) - ->lazyById(200, $column = 'id') + ->lazyById(200, column: 'id') ->each->update(['departed' => false]); ``` @@ -618,7 +631,7 @@ If the `ModelNotFoundException` is not caught, a 404 HTTP response is automatica ### Retrieving or Creating Models -The `firstOrCreate` method will attempt to locate a database record using the given column / value pairs. If the model can not 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 `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: @@ -830,7 +843,7 @@ So, to get started, you should define which model attributes you want to make ma /** * The attributes that are mass assignable. * - * @var array + * @var array */ protected $fillable = ['name']; } @@ -851,7 +864,7 @@ When assigning JSON columns, each column's mass assignable key must be specified /** * The attributes that are mass assignable. * - * @var array + * @var array */ protected $fillable = [ 'options->enabled', @@ -865,7 +878,7 @@ If you would like to make all of your attributes mass assignable, you may define /** * The attributes that aren't mass assignable. * - * @var array + * @var array|bool */ protected $guarded = []; @@ -895,9 +908,9 @@ Eloquent's `upsert` method may be used to update or create records in a single, ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] ], uniqueBy: ['departure', 'destination'], update: ['price']); - + > [!WARNING] -> All databases except SQL Server require the columns in the second argument of the `upsert` method to have a "primary" or "unique" index. In addition, the MySQL database driver ignores the second argument of the `upsert` method and always uses the "primary" and "unique" indexes of the table to detect existing records. +> 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 @@ -927,6 +940,10 @@ In the example above, we are retrieving the model from the database before calli Flight::destroy(collect([1, 2, 3])); +If you are utilizing [soft deleting models](#soft-deleting), you may permanently delete models via the `forceDestroy` method: + + Flight::forceDestroy(1); + > [!WARNING] > The `destroy` method loads each model individually and calls the `delete` method so that the `deleting` and `deleted` events are properly dispatched for each model. @@ -1510,7 +1527,7 @@ This command will place the new observer in your `app/Observers` directory. If t { // ... } - + /** * Handle the User "restored" event. */ diff --git a/encryption.md b/encryption.md index 02a07927207..49beb8cd179 100644 --- a/encryption.md +++ b/encryption.md @@ -8,7 +8,7 @@ ## 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 can not be modified or tampered with once encrypted. +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 @@ -65,7 +65,7 @@ You may encrypt a value using the `encryptString` method provided by the `Crypt` #### Decrypting a Value -You may decrypt values using the `decryptString` method provided by the `Crypt` facade. If the value can not be properly decrypted, such as when the message authentication code is invalid, an `Illuminate\Contracts\Encryption\DecryptException` will be thrown: +You may decrypt values using the `decryptString` method provided by the `Crypt` facade. If the value cannot be properly decrypted, such as when the message authentication code is invalid, an `Illuminate\Contracts\Encryption\DecryptException` will be thrown: use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Facades\Crypt; diff --git a/errors.md b/errors.md index 038caec7b8e..15c743d1fc6 100644 --- a/errors.md +++ b/errors.md @@ -167,6 +167,22 @@ When building your application, there will be some types of exceptions you never ]); }) +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: + +```php +withExceptions(function (Exceptions $exceptions) { $exceptions->render(function (InvalidOrderException $e, Request $request) { - return response()->view('errors.invalid-order', [], 500); + return response()->view('errors.invalid-order', status: 500); }); }) @@ -368,7 +384,6 @@ By default, limits will use the exception's class as the rate limit key. You can }); }) - Of course, you may return a mixture of `Lottery` and `Limit` instances for different exceptions: use App\Exceptions\ApiMonitoringException; @@ -411,3 +426,5 @@ 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/events.md b/events.md index 719b17954cf..dcfac0fe8f1 100644 --- a/events.md +++ b/events.md @@ -68,10 +68,20 @@ By default, Laravel will automatically find and register your event listeners by } } +You may listen to multiple events using PHP's union types: + + /** + * Handle the given event. + */ + public function handle(PodcastProcessed|PodcastPublished $event): void + { + // ... + } + If you plan to store your listeners in a different directory or within multiple directories, you may instruct Laravel to scan those directories using the `withEvents` method in your application's `bootstrap/app.php` file: ->withEvents(discover: [ - __DIR__.'/../app/Domain/Listeners', + __DIR__.'/../app/Domain/Orders/Listeners', ]) The `event:list` command may be used to list all of the listeners registered within your application: @@ -220,10 +230,7 @@ Next, let's take a look at the listener for our example event. Event listeners r /** * Create the event listener. */ - public function __construct() - { - // ... - } + public function __construct() {} /** * Handle the event. @@ -234,7 +241,7 @@ Next, let's take a look at the listener for our example event. Event listeners r } } -> [!NOTE] +> [!NOTE] > Your event listeners may also type-hint any dependencies they need on their constructors. All event listeners are resolved via the Laravel [service container](/docs/{{version}}/container), so dependencies will be injected automatically. @@ -328,7 +335,7 @@ If you would like to define the listener's queue connection, queue name, or dela #### 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 executed: +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: [!NOTE] +> [!NOTE] > To learn more about working around these issues, please review the documentation regarding [queued jobs and database transactions](/docs/{{version}}/queues#jobs-and-database-transactions). @@ -481,6 +487,40 @@ As an alternative to defining how many times a listener may be attempted before return now()->addMinutes(5); } + +#### Specifying Queued Listener Backoff + +If you would like to configure how many seconds Laravel should wait before retrying a listener that has encountered an exception, you may do so by defining a `backoff` property on your listener class: + + /** + * The number of seconds to wait before retrying the queued listener. + * + * @var int + */ + public $backoff = 3; + +If you require more complex logic for determining the listeners's backoff time, you may define a `backoff` method on your listener class: + + /** + * Calculate the number of seconds to wait before retrying the queued listener. + */ + public function backoff(): int + { + return 3; + } + +You may easily configure "exponential" backoffs by returning an array of backoff values from the `backoff` method. In this example, the retry delay will be 1 second for the first retry, 5 seconds for the second retry, 10 seconds for the third retry, and 10 seconds for every subsequent retry if there are more attempts remaining: + + /** + * Calculate the number of seconds to wait before retrying the queued listener. + * + * @return array + */ + public function backoff(): array + { + return [1, 5, 10]; + } + ## Dispatching Events @@ -513,13 +553,13 @@ To dispatch an event, you may call the static `dispatch` method on the event. Th } } - If you would like to conditionally dispatch an event, you may use the `dispatchIf` and `dispatchUnless` methods: +If you would like to conditionally dispatch an event, you may use the `dispatchIf` and `dispatchUnless` methods: OrderShipped::dispatchIf($condition, $order); OrderShipped::dispatchUnless($condition, $order); -> [!NOTE] +> [!NOTE] > When testing, it can be helpful to assert that certain events were dispatched without actually triggering their listeners. Laravel's [built-in testing helpers](#testing) make it a cinch. @@ -635,7 +675,7 @@ If your event listener methods are defined within the subscriber itself, you may ### Registering Event Subscribers -After writing the subscriber, you are ready to register it with the event dispatcher. You may register subscribers using the `subscribe` method of the `Event` facade. Typically, this should be done within the `boot` method of your application's `AppServiceProvider`: +After writing the subscriber, Laravel will automatically register handler methods within the subscriber if they follow Laravel's [event discovery conventions](#event-discovery). Otherwise, you may manually register your subscriber using the `subscribe` method of the `Event` facade. Typically, this should be done within the `boot` method of your application's `AppServiceProvider`: -Facade | Class | Service Container Binding -------------- | ------------- | ------------- -App | [Illuminate\Foundation\Application](https://laravel.com/api/{{version}}/Illuminate/Foundation/Application.html) | `app` -Artisan | [Illuminate\Contracts\Console\Kernel](https://laravel.com/api/{{version}}/Illuminate/Contracts/Console/Kernel.html) | `artisan` -Auth | [Illuminate\Auth\AuthManager](https://laravel.com/api/{{version}}/Illuminate/Auth/AuthManager.html) | `auth` -Auth (Instance) | [Illuminate\Contracts\Auth\Guard](https://laravel.com/api/{{version}}/Illuminate/Contracts/Auth/Guard.html) | `auth.driver` -Blade | [Illuminate\View\Compilers\BladeCompiler](https://laravel.com/api/{{version}}/Illuminate/View/Compilers/BladeCompiler.html) | `blade.compiler` -Broadcast | [Illuminate\Contracts\Broadcasting\Factory](https://laravel.com/api/{{version}}/Illuminate/Contracts/Broadcasting/Factory.html) |   -Broadcast (Instance) | [Illuminate\Contracts\Broadcasting\Broadcaster](https://laravel.com/api/{{version}}/Illuminate/Contracts/Broadcasting/Broadcaster.html) |   -Bus | [Illuminate\Contracts\Bus\Dispatcher](https://laravel.com/api/{{version}}/Illuminate/Contracts/Bus/Dispatcher.html) |   -Cache | [Illuminate\Cache\CacheManager](https://laravel.com/api/{{version}}/Illuminate/Cache/CacheManager.html) | `cache` -Cache (Instance) | [Illuminate\Cache\Repository](https://laravel.com/api/{{version}}/Illuminate/Cache/Repository.html) | `cache.store` -Config | [Illuminate\Config\Repository](https://laravel.com/api/{{version}}/Illuminate/Config/Repository.html) | `config` -Cookie | [Illuminate\Cookie\CookieJar](https://laravel.com/api/{{version}}/Illuminate/Cookie/CookieJar.html) | `cookie` -Crypt | [Illuminate\Encryption\Encrypter](https://laravel.com/api/{{version}}/Illuminate/Encryption/Encrypter.html) | `encrypter` -Date | [Illuminate\Support\DateFactory](https://laravel.com/api/{{version}}/Illuminate/Support/DateFactory.html) | `date` -DB | [Illuminate\Database\DatabaseManager](https://laravel.com/api/{{version}}/Illuminate/Database/DatabaseManager.html) | `db` -DB (Instance) | [Illuminate\Database\Connection](https://laravel.com/api/{{version}}/Illuminate/Database/Connection.html) | `db.connection` -Event | [Illuminate\Events\Dispatcher](https://laravel.com/api/{{version}}/Illuminate/Events/Dispatcher.html) | `events` -File | [Illuminate\Filesystem\Filesystem](https://laravel.com/api/{{version}}/Illuminate/Filesystem/Filesystem.html) | `files` -Gate | [Illuminate\Contracts\Auth\Access\Gate](https://laravel.com/api/{{version}}/Illuminate/Contracts/Auth/Access/Gate.html) |   -Hash | [Illuminate\Contracts\Hashing\Hasher](https://laravel.com/api/{{version}}/Illuminate/Contracts/Hashing/Hasher.html) | `hash` -Http | [Illuminate\Http\Client\Factory](https://laravel.com/api/{{version}}/Illuminate/Http/Client/Factory.html) |   -Lang | [Illuminate\Translation\Translator](https://laravel.com/api/{{version}}/Illuminate/Translation/Translator.html) | `translator` -Log | [Illuminate\Log\LogManager](https://laravel.com/api/{{version}}/Illuminate/Log/LogManager.html) | `log` -Mail | [Illuminate\Mail\Mailer](https://laravel.com/api/{{version}}/Illuminate/Mail/Mailer.html) | `mailer` -Notification | [Illuminate\Notifications\ChannelManager](https://laravel.com/api/{{version}}/Illuminate/Notifications/ChannelManager.html) |   -Password | [Illuminate\Auth\Passwords\PasswordBrokerManager](https://laravel.com/api/{{version}}/Illuminate/Auth/Passwords/PasswordBrokerManager.html) | `auth.password` -Password (Instance) | [Illuminate\Auth\Passwords\PasswordBroker](https://laravel.com/api/{{version}}/Illuminate/Auth/Passwords/PasswordBroker.html) | `auth.password.broker` -Pipeline (Instance) | [Illuminate\Pipeline\Pipeline](https://laravel.com/api/{{version}}/Illuminate/Pipeline/Pipeline.html) |   -Process | [Illuminate\Process\Factory](https://laravel.com/api/{{version}}/Illuminate/Process/Factory.html) |   -Queue | [Illuminate\Queue\QueueManager](https://laravel.com/api/{{version}}/Illuminate/Queue/QueueManager.html) | `queue` -Queue (Instance) | [Illuminate\Contracts\Queue\Queue](https://laravel.com/api/{{version}}/Illuminate/Contracts/Queue/Queue.html) | `queue.connection` -Queue (Base Class) | [Illuminate\Queue\Queue](https://laravel.com/api/{{version}}/Illuminate/Queue/Queue.html) |   -RateLimiter | [Illuminate\Cache\RateLimiter](https://laravel.com/api/{{version}}/Illuminate/Cache/RateLimiter.html) |   -Redirect | [Illuminate\Routing\Redirector](https://laravel.com/api/{{version}}/Illuminate/Routing/Redirector.html) | `redirect` -Redis | [Illuminate\Redis\RedisManager](https://laravel.com/api/{{version}}/Illuminate/Redis/RedisManager.html) | `redis` -Redis (Instance) | [Illuminate\Redis\Connections\Connection](https://laravel.com/api/{{version}}/Illuminate/Redis/Connections/Connection.html) | `redis.connection` -Request | [Illuminate\Http\Request](https://laravel.com/api/{{version}}/Illuminate/Http/Request.html) | `request` -Response | [Illuminate\Contracts\Routing\ResponseFactory](https://laravel.com/api/{{version}}/Illuminate/Contracts/Routing/ResponseFactory.html) |   -Response (Instance) | [Illuminate\Http\Response](https://laravel.com/api/{{version}}/Illuminate/Http/Response.html) |   -Route | [Illuminate\Routing\Router](https://laravel.com/api/{{version}}/Illuminate/Routing/Router.html) | `router` -Schema | [Illuminate\Database\Schema\Builder](https://laravel.com/api/{{version}}/Illuminate/Database/Schema/Builder.html) |   -Session | [Illuminate\Session\SessionManager](https://laravel.com/api/{{version}}/Illuminate/Session/SessionManager.html) | `session` -Session (Instance) | [Illuminate\Session\Store](https://laravel.com/api/{{version}}/Illuminate/Session/Store.html) | `session.store` -Storage | [Illuminate\Filesystem\FilesystemManager](https://laravel.com/api/{{version}}/Illuminate/Filesystem/FilesystemManager.html) | `filesystem` -Storage (Instance) | [Illuminate\Contracts\Filesystem\Filesystem](https://laravel.com/api/{{version}}/Illuminate/Contracts/Filesystem/Filesystem.html) | `filesystem.disk` -URL | [Illuminate\Routing\UrlGenerator](https://laravel.com/api/{{version}}/Illuminate/Routing/UrlGenerator.html) | `url` -Validator | [Illuminate\Validation\Factory](https://laravel.com/api/{{version}}/Illuminate/Validation/Factory.html) | `validator` -Validator (Instance) | [Illuminate\Validation\Validator](https://laravel.com/api/{{version}}/Illuminate/Validation/Validator.html) |   -View | [Illuminate\View\Factory](https://laravel.com/api/{{version}}/Illuminate/View/Factory.html) | `view` -View (Instance) | [Illuminate\View\View](https://laravel.com/api/{{version}}/Illuminate/View/View.html) |   -Vite | [Illuminate\Foundation\Vite](https://laravel.com/api/{{version}}/Illuminate/Foundation/Vite.html) |   +| Facade | Class | Service Container Binding | +| --- | --- | --- | +| App | [Illuminate\Foundation\Application](https://laravel.com/api/{{version}}/Illuminate/Foundation/Application.html) | `app` | +| Artisan | [Illuminate\Contracts\Console\Kernel](https://laravel.com/api/{{version}}/Illuminate/Contracts/Console/Kernel.html) | `artisan` | +| Auth (Instance) | [Illuminate\Contracts\Auth\Guard](https://laravel.com/api/{{version}}/Illuminate/Contracts/Auth/Guard.html) | `auth.driver` | +| Auth | [Illuminate\Auth\AuthManager](https://laravel.com/api/{{version}}/Illuminate/Auth/AuthManager.html) | `auth` | +| Blade | [Illuminate\View\Compilers\BladeCompiler](https://laravel.com/api/{{version}}/Illuminate/View/Compilers/BladeCompiler.html) | `blade.compiler` | +| Broadcast (Instance) | [Illuminate\Contracts\Broadcasting\Broadcaster](https://laravel.com/api/{{version}}/Illuminate/Contracts/Broadcasting/Broadcaster.html) |   | +| Broadcast | [Illuminate\Contracts\Broadcasting\Factory](https://laravel.com/api/{{version}}/Illuminate/Contracts/Broadcasting/Factory.html) |   | +| Bus | [Illuminate\Contracts\Bus\Dispatcher](https://laravel.com/api/{{version}}/Illuminate/Contracts/Bus/Dispatcher.html) |   | +| Cache (Instance) | [Illuminate\Cache\Repository](https://laravel.com/api/{{version}}/Illuminate/Cache/Repository.html) | `cache.store` | +| Cache | [Illuminate\Cache\CacheManager](https://laravel.com/api/{{version}}/Illuminate/Cache/CacheManager.html) | `cache` | +| Config | [Illuminate\Config\Repository](https://laravel.com/api/{{version}}/Illuminate/Config/Repository.html) | `config` | +| Context | [Illuminate\Log\Context\Repository](https://laravel.com/api/{{version}}/Illuminate/Log/Context/Repository.html) |   | +| Cookie | [Illuminate\Cookie\CookieJar](https://laravel.com/api/{{version}}/Illuminate/Cookie/CookieJar.html) | `cookie` | +| Crypt | [Illuminate\Encryption\Encrypter](https://laravel.com/api/{{version}}/Illuminate/Encryption/Encrypter.html) | `encrypter` | +| Date | [Illuminate\Support\DateFactory](https://laravel.com/api/{{version}}/Illuminate/Support/DateFactory.html) | `date` | +| DB (Instance) | [Illuminate\Database\Connection](https://laravel.com/api/{{version}}/Illuminate/Database/Connection.html) | `db.connection` | +| DB | [Illuminate\Database\DatabaseManager](https://laravel.com/api/{{version}}/Illuminate/Database/DatabaseManager.html) | `db` | +| Event | [Illuminate\Events\Dispatcher](https://laravel.com/api/{{version}}/Illuminate/Events/Dispatcher.html) | `events` | +| Exceptions (Instance) | [Illuminate\Contracts\Debug\ExceptionHandler](https://laravel.com/api/{{version}}/Illuminate/Contracts/Debug/ExceptionHandler.html) |   | +| Exceptions | [Illuminate\Foundation\Exceptions\Handler](https://laravel.com/api/{{version}}/Illuminate/Foundation/Exceptions/Handler.html) |   | +| File | [Illuminate\Filesystem\Filesystem](https://laravel.com/api/{{version}}/Illuminate/Filesystem/Filesystem.html) | `files` | +| Gate | [Illuminate\Contracts\Auth\Access\Gate](https://laravel.com/api/{{version}}/Illuminate/Contracts/Auth/Access/Gate.html) |   | +| Hash | [Illuminate\Contracts\Hashing\Hasher](https://laravel.com/api/{{version}}/Illuminate/Contracts/Hashing/Hasher.html) | `hash` | +| Http | [Illuminate\Http\Client\Factory](https://laravel.com/api/{{version}}/Illuminate/Http/Client/Factory.html) |   | +| Lang | [Illuminate\Translation\Translator](https://laravel.com/api/{{version}}/Illuminate/Translation/Translator.html) | `translator` | +| Log | [Illuminate\Log\LogManager](https://laravel.com/api/{{version}}/Illuminate/Log/LogManager.html) | `log` | +| Mail | [Illuminate\Mail\Mailer](https://laravel.com/api/{{version}}/Illuminate/Mail/Mailer.html) | `mailer` | +| Notification | [Illuminate\Notifications\ChannelManager](https://laravel.com/api/{{version}}/Illuminate/Notifications/ChannelManager.html) |   | +| Password (Instance) | [Illuminate\Auth\Passwords\PasswordBroker](https://laravel.com/api/{{version}}/Illuminate/Auth/Passwords/PasswordBroker.html) | `auth.password.broker` | +| Password | [Illuminate\Auth\Passwords\PasswordBrokerManager](https://laravel.com/api/{{version}}/Illuminate/Auth/Passwords/PasswordBrokerManager.html) | `auth.password` | +| Pipeline (Instance) | [Illuminate\Pipeline\Pipeline](https://laravel.com/api/{{version}}/Illuminate/Pipeline/Pipeline.html) |   | +| Process | [Illuminate\Process\Factory](https://laravel.com/api/{{version}}/Illuminate/Process/Factory.html) |   | +| Queue (Base Class) | [Illuminate\Queue\Queue](https://laravel.com/api/{{version}}/Illuminate/Queue/Queue.html) |   | +| Queue (Instance) | [Illuminate\Contracts\Queue\Queue](https://laravel.com/api/{{version}}/Illuminate/Contracts/Queue/Queue.html) | `queue.connection` | +| Queue | [Illuminate\Queue\QueueManager](https://laravel.com/api/{{version}}/Illuminate/Queue/QueueManager.html) | `queue` | +| RateLimiter | [Illuminate\Cache\RateLimiter](https://laravel.com/api/{{version}}/Illuminate/Cache/RateLimiter.html) |   | +| Redirect | [Illuminate\Routing\Redirector](https://laravel.com/api/{{version}}/Illuminate/Routing/Redirector.html) | `redirect` | +| Redis (Instance) | [Illuminate\Redis\Connections\Connection](https://laravel.com/api/{{version}}/Illuminate/Redis/Connections/Connection.html) | `redis.connection` | +| Redis | [Illuminate\Redis\RedisManager](https://laravel.com/api/{{version}}/Illuminate/Redis/RedisManager.html) | `redis` | +| Request | [Illuminate\Http\Request](https://laravel.com/api/{{version}}/Illuminate/Http/Request.html) | `request` | +| Response (Instance) | [Illuminate\Http\Response](https://laravel.com/api/{{version}}/Illuminate/Http/Response.html) |   | +| Response | [Illuminate\Contracts\Routing\ResponseFactory](https://laravel.com/api/{{version}}/Illuminate/Contracts/Routing/ResponseFactory.html) |   | +| Route | [Illuminate\Routing\Router](https://laravel.com/api/{{version}}/Illuminate/Routing/Router.html) | `router` | +| Schedule | [Illuminate\Console\Scheduling\Schedule](https://laravel.com/api/{{version}}/Illuminate/Console/Scheduling/Schedule.html) |   | +| Schema | [Illuminate\Database\Schema\Builder](https://laravel.com/api/{{version}}/Illuminate/Database/Schema/Builder.html) |   | +| Session (Instance) | [Illuminate\Session\Store](https://laravel.com/api/{{version}}/Illuminate/Session/Store.html) | `session.store` | +| Session | [Illuminate\Session\SessionManager](https://laravel.com/api/{{version}}/Illuminate/Session/SessionManager.html) | `session` | +| Storage (Instance) | [Illuminate\Contracts\Filesystem\Filesystem](https://laravel.com/api/{{version}}/Illuminate/Contracts/Filesystem/Filesystem.html) | `filesystem.disk` | +| Storage | [Illuminate\Filesystem\FilesystemManager](https://laravel.com/api/{{version}}/Illuminate/Filesystem/FilesystemManager.html) | `filesystem` | +| URL | [Illuminate\Routing\UrlGenerator](https://laravel.com/api/{{version}}/Illuminate/Routing/UrlGenerator.html) | `url` | +| Validator (Instance) | [Illuminate\Validation\Validator](https://laravel.com/api/{{version}}/Illuminate/Validation/Validator.html) |   | +| Validator | [Illuminate\Validation\Factory](https://laravel.com/api/{{version}}/Illuminate/Validation/Factory.html) | `validator` | +| View (Instance) | [Illuminate\View\View](https://laravel.com/api/{{version}}/Illuminate/View/View.html) |   | +| View | [Illuminate\View\Factory](https://laravel.com/api/{{version}}/Illuminate/View/Factory.html) | `view` | +| Vite | [Illuminate\Foundation\Vite](https://laravel.com/api/{{version}}/Illuminate/Foundation/Vite.html) |   | diff --git a/filesystem.md b/filesystem.md index 659db7882a7..79cfa25fb16 100644 --- a/filesystem.md +++ b/filesystem.md @@ -54,7 +54,7 @@ When using the `local` driver, all file operations are relative to the `root` di 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`. -To make these files accessible from the web, you should create a symbolic link from `public/storage` to `storage/app/public`. Utilizing this folder convention will keep your publicly accessible files in one directory that can be easily shared across deployments when using zero down-time deployment systems like [Envoyer](https://envoyer.io). +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`. Utilizing this folder convention will keep your publicly accessible files in one directory that can be easily shared across deployments when using zero down-time deployment systems like [Envoyer](https://envoyer.io). To create the symbolic link, you may use the `storage:link` Artisan command: @@ -219,7 +219,7 @@ AWS_URL=http://localhost:9000/local ``` > [!WARNING] -> Generating temporary storage URLs via the `temporaryUrl` method is not supported when using MinIO. +> 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 @@ -312,7 +312,7 @@ If you would like to modify the host for URLs generated using the `Storage` faca ### Temporary URLs -Using the `temporaryUrl` method, you may create temporary URLs to files stored using the `s3` driver. This method accepts a path and a `DateTime` instance specifying when the URL should expire: +Using the `temporaryUrl` method, you may create temporary URLs to files stored using the `local` and `s3` drivers. This method accepts a path and a `DateTime` instance specifying when the URL should expire: use Illuminate\Support\Facades\Storage; @@ -320,6 +320,23 @@ Using the `temporaryUrl` method, you may create temporary URLs to files stored u '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: + +```php +'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => true, // [tl! add] + '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: $url = Storage::temporaryUrl( @@ -331,6 +348,9 @@ If you need to specify additional [S3 request parameters](https://docs.aws.amazo ] ); + +#### 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: 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'); }); @@ -714,6 +737,9 @@ class ExampleTest extends TestCase 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'); } diff --git a/folio.md b/folio.md index 13406521094..9efb7edc619 100644 --- a/folio.md +++ b/folio.md @@ -109,7 +109,7 @@ php artisan folio:list 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: ```bash -php artisan make:folio user/profile +php artisan folio:page user/profile # pages/user/profile.blade.php → /user/profile ``` @@ -120,10 +120,10 @@ php artisan make:folio user/profile 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: ```bash -php artisan make:folio index +php artisan folio:page index # pages/index.blade.php → / -php artisan make:folio users/index +php artisan folio:page users/index # pages/users/index.blade.php → /users ``` @@ -133,7 +133,7 @@ php artisan make:folio users/index 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: ```bash -php artisan make:folio "users/[id]" +php artisan folio:page "users/[id]" # pages/users/[id].blade.php → /users/1 ``` @@ -149,7 +149,7 @@ Captured segments can be accessed as variables within your Blade template: To capture multiple segments, you can prefix the encapsulated segment with three dots `...`: ```bash -php artisan make:folio "users/[...ids]" +php artisan folio:page "users/[...ids]" # pages/users/[...ids].blade.php → /users/1/2/3 ``` @@ -170,7 +170,7 @@ When capturing multiple segments, the captured segments will be injected into th 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: ```bash -php artisan make:folio "users/[User]" +php artisan folio:page "users/[User]" # pages/users/[User].blade.php → /users/1 ``` @@ -194,7 +194,7 @@ On Windows, you should use `-` to separate the model name from the key: `[Post-s 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: ```bash -php artisan make:folio "users/[.App.Models.User]" +php artisan folio:page "users/[.App.Models.User]" # pages/users/[.App.Models.User].blade.php → /users/1 ``` diff --git a/fortify.md b/fortify.md index 4a0b0bf547c..d6e271a0c72 100644 --- a/fortify.md +++ b/fortify.md @@ -189,15 +189,18 @@ The example below contains the default pipeline definition that you may use as a ```php 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, @@ -205,6 +208,15 @@ Fortify::authenticateThrough(function (Request $request) { }); ``` +#### 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/{{version}}/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. + +> [!NOTE] +> Utilizing a mixture of throttling, [two factor authentication](/docs/{{version}}/fortify#two-factor-authentication), and an external web application firewall (WAF) will provide the most robust defense for your legitimate application users. + ### Customizing Redirects @@ -524,7 +536,7 @@ If the request to resend the verification link email was successful, Fortify wil ### 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\Routing\Middleware\ValidateSignature` middleware: +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: ```php Route::get('/dashboard', function () { diff --git a/helpers.md b/helpers.md index 23cd72599c4..bde5d85235d 100644 --- a/helpers.md +++ b/helpers.md @@ -5,6 +5,7 @@ - [Other Utilities](#other-utilities) - [Benchmarking](#benchmarking) - [Dates](#dates) + - [Deferred Functions](#deferred-functions) - [Lottery](#lottery) - [Pipeline](#pipeline) - [Sleep](#sleep) @@ -69,7 +70,6 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct [Arr::sort](#method-array-sort) [Arr::sortDesc](#method-array-sort-desc) [Arr::sortRecursive](#method-array-sort-recursive) -[Arr::sortRecursiveDesc](#method-array-sort-recursive-desc) [Arr::take](#method-array-take) [Arr::toCssClasses](#method-array-to-css-classes) [Arr::toCssStyles](#method-array-to-css-styles) @@ -93,18 +93,23 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct [Number::abbreviate](#method-number-abbreviate) [Number::clamp](#method-number-clamp) [Number::currency](#method-number-currency) +[Number::defaultCurrency](#method-default-currency) +[Number::defaultLocale](#method-default-locale) [Number::fileSize](#method-number-file-size) [Number::forHumans](#method-number-for-humans) [Number::format](#method-number-format) [Number::ordinal](#method-number-ordinal) +[Number::pairs](#method-number-pairs) [Number::percentage](#method-number-percentage) [Number::spell](#method-number-spell) +[Number::trim](#method-number-trim) [Number::useLocale](#method-number-use-locale) [Number::withLocale](#method-number-with-locale) +[Number::useCurrency](#method-number-use-currency) +[Number::withCurrency](#method-number-with-currency) - ### Paths @@ -198,6 +203,7 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct [value](#method-value) [view](#method-view) [with](#method-with) +[when](#method-when) @@ -243,7 +249,6 @@ The `Arr::add` method adds a given key / value pair to an array if the given key // ['name' => 'Desk', 'price' => 100] - #### `Arr::collapse()` {.collection-method} @@ -1218,15 +1223,37 @@ The `Number::currency` method returns the currency representation of the given v $currency = Number::currency(1000); - // $1,000 + // $1,000.00 $currency = Number::currency(1000, in: 'EUR'); - // €1,000 + // €1,000.00 $currency = Number::currency(1000, in: 'EUR', locale: 'de'); - // 1.000 € + // 1.000,00 € + + +#### `Number::defaultCurrency()` {.collection-method} + +The `Number::defaultCurrency` method returns the default currency being used by the `Number` class: + + use Illuminate\Support\Number; + + $currency = Number::defaultCurrency(); + + // USD + + +#### `Number::defaultLocale()` {.collection-method} + +The `Number::defaultLocale` method returns the default locale being used by the `Number` class: + + use Illuminate\Support\Number; + + $locale = Number::defaultLocale(); + + // en #### `Number::fileSize()` {.collection-method} @@ -1308,6 +1335,23 @@ The `Number::ordinal` method returns a number's ordinal representation: // 21st + +#### `Number::pairs()` {.collection-method} + +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: + +```php +use Illuminate\Support\Number; + +$result = Number::pairs(25, 10); + +// [[1, 10], [11, 20], [21, 25]] + +$result = Number::pairs(25, 10, offset: 0); + +// [[0, 10], [10, 20], [20, 25]] +``` + #### `Number::percentage()` {.collection-method} @@ -1346,7 +1390,6 @@ The `Number::spell` method transforms the given number into a string of words: // quatre-vingt-huit - The `after` argument allows you to specify a value after which all numbers should be spelled out: $number = Number::spell(10, after: 10); @@ -1367,6 +1410,21 @@ The `until` argument allows you to specify a value before which all numbers shou // 10 + +#### `Number::trim()` {.collection-method} + +The `Number::trim` method removes any trailing zero digits after the decimal point of the given number: + + use Illuminate\Support\Number; + + $number = Number::trim(12.0); + + // 12 + + $number = Number::trim(12.30); + + // 12.3 + #### `Number::useLocale()` {.collection-method} @@ -1393,6 +1451,32 @@ The `Number::withLocale` method executes the given closure using the specified l return Number::format(1500); }); + +#### `Number::useCurrency()` {.collection-method} + +The `Number::useCurrency` method sets the default number currency globally, which affects how the currency is formatted by subsequent invocations to the `Number` class's methods: + + use Illuminate\Support\Number; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Number::useCurrency('GBP'); + } + + +#### `Number::withCurrency()` {.collection-method} + +The `Number::withCurrency` method executes the given closure using the specified currency and then restores the original currency after the callback has executed: + + use Illuminate\Support\Number; + + $number = Number::withCurrency('GBP', function () { + // ... + }); + ## Paths @@ -1572,7 +1656,7 @@ If no path is provided, an `Illuminate\Routing\UrlGenerator` instance is returne #### `abort()` {.collection-method} -The `abort` function throws [an HTTP exception](/docs/{{version}}/errors#http-exceptions) which will be rendered by the [exception handler](/docs/{{version}}/errors#the-exception-handler): +The `abort` function throws [an HTTP exception](/docs/{{version}}/errors#http-exceptions) which will be rendered by the [exception handler](/docs/{{version}}/errors#handling-exceptions): abort(403); @@ -1714,7 +1798,7 @@ The `context` function gets the value from the [current context](/docs/{{version $value = context('trace_id'); - $value = config('trace_id', $default); + $value = context('trace_id', $default); You may set context values by passing an array of key / value pairs: @@ -1867,7 +1951,7 @@ An array of contextual data may also be passed to the function: #### `literal()` {.collection-method} -"The `literal` function creates a new [stdClass](https://www.php.net/manual/en/class.stdclass.php) instance with the given named arguments as properties: +The `literal` function creates a new [stdClass](https://www.php.net/manual/en/class.stdclass.php) instance with the given named arguments as properties: $obj = literal( name: 'Joe', @@ -1888,7 +1972,7 @@ An array of contextual data may also be passed to the function: logger('User has logged in.', ['id' => $user->id]); -A [logger](/docs/{{version}}/errors#logging) instance will be returned if no value is passed to the function: +A [logger](/docs/{{version}}/logging) instance will be returned if no value is passed to the function: logger()->error('You are not allowed here.'); @@ -2000,7 +2084,7 @@ The `redirect` function returns a [redirect HTTP response](/docs/{{version}}/res #### `report()` {.collection-method} -The `report` function will report an exception using your [exception handler](/docs/{{version}}/errors#the-exception-handler): +The `report` function will report an exception using your [exception handler](/docs/{{version}}/errors#handling-exceptions): report($e); @@ -2011,7 +2095,7 @@ The `report` function also accepts a string as an argument. When a string is giv #### `report_if()` {.collection-method} -The `report_if` function will report an exception using your [exception handler](/docs/{{version}}/errors#the-exception-handler) if the given condition is `true`: +The `report_if` function will report an exception using your [exception handler](/docs/{{version}}/errors#handling-exceptions) if the given condition is `true`: report_if($shouldReport, $e); @@ -2020,7 +2104,7 @@ The `report_if` function will report an exception using your [exception handler] #### `report_unless()` {.collection-method} -The `report_unless` function will report an exception using your [exception handler](/docs/{{version}}/errors#the-exception-handler) if the given condition is `false`: +The `report_unless` function will report an exception using your [exception handler](/docs/{{version}}/errors#handling-exceptions) if the given condition is `false`: report_unless($reportingDisabled, $e); @@ -2038,7 +2122,7 @@ The `request` function returns the current [request](/docs/{{version}}/requests) #### `rescue()` {.collection-method} -The `rescue` function executes the given closure and catches any exceptions that occur during its execution. All exceptions that are caught will be sent to your [exception handler](/docs/{{version}}/errors#the-exception-handler); however, the request will continue processing: +The `rescue` function executes the given closure and catches any exceptions that occur during its execution. All exceptions that are caught will be sent to your [exception handler](/docs/{{version}}/errors#handling-exceptions); however, the request will continue processing: return rescue(function () { return $this->method(); @@ -2242,7 +2326,7 @@ Additional arguments may be passed to the `value` function. If the first argumen $result = value(function (string $name) { return $name; }, 'Taylor'); - + // 'Taylor' @@ -2273,6 +2357,23 @@ The `with` function returns the value it is given. If a closure is passed as the // 5 + +#### `when()` {.collection-method} + +The `when` function returns the value it is given if a given condition evaluates to `true`. Otherwise, `null` is returned. If a closure is passed as the second argument to the function, the closure will be executed and its returned value will be returned: + + $value = when(true, 'Hello World'); + + $value = when(true, fn () => 'Hello World'); + +The `when` function is primarily useful for conditionally rendering HTML attributes: + +```blade +
+ ... +
+``` + ## Other Utilities @@ -2322,6 +2423,108 @@ $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 + +> [!WARNING] +> Deferred functions are currently in beta while we gather community feedback. + +While Laravel's [queued jobs](/docs/{{version}}/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: + +```php +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: + +```php +defer(fn () => Metrics::reportOrder($order))->always(); +``` + + +#### 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: + +```php +defer(fn () => Metrics::report(), 'reportMetrics'); + +defer()->forget('reportMetrics'); +``` + + +#### Deferred Function Compatibility + +If you upgraded to Laravel 11.x from a Laravel 10.x application and your application's skeleton still contains an `app/Http/Kernel.php` file, you should add the `InvokeDeferredCallbacks` middleware to the beginning of the kernel's `$middleware` property: + +```php +protected $middleware = [ + \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class, // [tl! add] + \App\Http\Middleware\TrustProxies::class, + // ... +]; +``` + + +#### 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: + +```php tab=Pest +test('without defer', function () { + $this->withoutDefer(); + + // ... +}); +``` + +```php tab=PHPUnit +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: + +```php +withoutDefer(); + }// [tl! add:end] +} +``` + ### Lottery @@ -2421,6 +2624,12 @@ Laravel's `Sleep` class is a light-weight wrapper around PHP's native `sleep` an The `Sleep` class offers a variety of methods that allow you to work with different units of time: + // Return a value after sleeping... + $result = Sleep::for(1)->second()->then(fn () => 1 + 1); + + // Sleep while a given value is true... + Sleep::for(1)->second()->while(fn () => shouldKeepSleeping()); + // Pause execution for 90 seconds... Sleep::for(1.5)->minutes(); @@ -2499,7 +2708,7 @@ it('checks if ready three times', function () { ``` ```php tab=PHPUnit -public function test_it_checks_if_ready_four_times() +public function test_it_checks_if_ready_three_times() { Sleep::fake(); diff --git a/horizon.md b/horizon.md index 0d55a87780c..fc2a090c627 100644 --- a/horizon.md +++ b/horizon.md @@ -74,6 +74,18 @@ After installation, the primary Horizon configuration option that you should fam ], ], +You may also define a wildcard environment (`*`) which will be used when no other matching environment is found: + + '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/{{version}}/configuration#determining-the-current-environment). For example, the default `local` Horizon environment is configured to start three worker processes and automatically balance the number of worker processes assigned to each queue. The default `production` environment is configured to start a maximum of 10 worker processes and automatically balance the number of worker processes assigned to each queue. > [!WARNING] @@ -89,7 +101,7 @@ You may add additional supervisors to a given environment if you would like to d #### Maintenance Mode -While your application is in [maintainance mode](/docs/{{version}}/configuration#maintenance-mode), queued jobs will not be processed by Horizon unless the supervisor's `force` option is defined as `true` within the Horizon configuration file: +While your application is in [maintenance mode](/docs/{{version}}/configuration#maintenance-mode), queued jobs will not be processed by Horizon unless the supervisor's `force` option is defined as `true` within the Horizon configuration file: 'environments' => [ 'production' => [ @@ -114,7 +126,7 @@ Unlike Laravel's default queue system, Horizon allows you to choose from three w The `auto` strategy, which is the configuration file's default, 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 `render` queue is empty, Horizon will allocate more workers to your `notifications` queue until the queue is empty. -When using the `auto` strategy, you may define the `minProcesses` and `maxProcesses` configuration options to control the minimum and the maximum number of worker processes Horizon should scale up and down to: +When using the `auto` strategy, you may define the `minProcesses` and `maxProcesses` configuration options to control the minimum number of processes per queue and the maximum number of worker processes in total Horizon should scale up and down to: 'environments' => [ 'production' => [ @@ -177,7 +189,7 @@ Alternatively, the job you wish to silence can implement the `Laravel\Horizon\Co class ProcessPodcast implements ShouldQueue, Silenced { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Queueable; // ... } @@ -185,23 +197,7 @@ Alternatively, the job you wish to silence can implement the `Laravel\Horizon\Co ## 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). In addition, when upgrading to any new Horizon version, you should re-publish Horizon's assets: - -```shell -php artisan horizon:publish -``` - -To keep the assets up-to-date and avoid issues in future updates, you may add the `vendor:publish --tag=laravel-assets` command to the `post-update-cmd` scripts in your application's `composer.json` file: - -```json -{ - "scripts": { - "post-update-cmd": [ - "@php artisan vendor:publish --tag=laravel-assets --ansi --force" - ] - } -} -``` +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 @@ -234,7 +230,13 @@ You may check the current status of the Horizon process using the `horizon:statu php artisan horizon:status ``` -You may gracefully terminate the Horizon process using the `horizon:terminate` Artisan command. Any jobs that are currently being processed by will be completed and then Horizon will stop executing: +You may check the current status of a specific Horizon [supervisor](#supervisors) using the `horizon:supervisor-status` Artisan command: + +```shell +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: ```shell php artisan horizon:terminate @@ -311,15 +313,12 @@ Horizon allows you to assign “tags” to jobs, including mailables, broadcast namespace App\Jobs; use App\Models\Video; - use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; - use Illuminate\Foundation\Bus\Dispatchable; - use Illuminate\Queue\InteractsWithQueue; - use Illuminate\Queue\SerializesModels; + use Illuminate\Foundation\Queue\Queueable; class RenderVideo implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Queueable; /** * Create a new job instance. @@ -382,7 +381,6 @@ When retrieving the tags for a queued event listener, Horizon will automatically } } - ## Notifications @@ -432,6 +430,12 @@ If you would like to delete a failed job, you may use the `horizon:forget` comma 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: + +```shell +php artisan horizon:forget --all +``` + ## Clearing Jobs From Queues diff --git a/http-client.md b/http-client.md index c49aaa57d8f..34b8396c730 100644 --- a/http-client.md +++ b/http-client.md @@ -35,9 +35,10 @@ To make requests, you may use the `head`, `get`, `post`, `put`, `patch`, and `de The `get` method returns an instance of `Illuminate\Http\Client\Response`, which provides a variety of methods that may be used to inspect the response: $response->body() : string; - $response->json($key = null, $default = null) : array|mixed; + $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; @@ -336,6 +337,16 @@ If you would like to perform some additional logic before the exception is throw // ... })->json(); +By default, `RequestException` messages are truncated to 120 characters when logged or reported. To customize or disable this behavior, you may utilize the `truncateRequestExceptionsAt` and `dontTruncateRequestExceptions` methods when configuring your application's exception handling behavior in your `bootstrap/app.php` file: + + ->withExceptions(function (Exceptions $exceptions) { + // Truncate request exception messages to 240 characters... + $exceptions->truncateRequestExceptionsAt(240); + + // Disable request exception message truncation... + $exceptions->dontTruncateRequestExceptions(); + }) + ### Guzzle Middleware @@ -529,6 +540,23 @@ If you would like to specify a fallback URL pattern that will stub all unmatched '*' => Http::response('Hello World', 200, ['Headers']), ]); +For convenience, simple string, JSON, and empty responses may be generated by providing a string, array, or integer as the response: + + Http::fake([ + 'google.com/*' => 'Hello World', + 'github.com/*' => ['foo' => 'bar'], + 'chatgpt.com/*' => 200, + ]); + + +#### Faking Connection Exceptions + +Sometimes you may need to test your application's behavior if the HTTP client encounters an `Illuminate\Http\Client\ConnectionException` when attempting to make a request. You can instruct the HTTP client to throw a connection exception using the `failedConnection` method: + + Http::fake([ + 'github.com/*' => Http::failedConnection(), + ]); + #### Faking Response Sequences diff --git a/http-tests.md b/http-tests.md index b645e30b1e8..4bb481f31e8 100644 --- a/http-tests.md +++ b/http-tests.md @@ -95,7 +95,7 @@ class ExampleTest extends TestCase 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. -> [!NOTE] +> [!NOTE] > For convenience, the CSRF middleware is automatically disabled when running tests. @@ -426,6 +426,15 @@ $this->assertThrows( ); ``` +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: + +```php +$this->assertThrows( + fn () => (new ProcessOrder)->execute(), + fn (OrderInvalid $e) => $e->orderId() === 123; +); +``` + ## Testing JSON APIs @@ -480,8 +489,8 @@ expect($response['created'])->toBeTrue(); $this->assertTrue($response['created']); ``` -> [!NOTE] -> The `assertJson` method converts the response to an array and utilizes `PHPUnit::assertArraySubset` 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. +> [!NOTE] +> 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 @@ -878,11 +887,6 @@ You may use the `component` method to evaluate and render a [Blade component](/d $view->assertSee('Taylor'); - -## Testing Exceptions - -If you are testing a - ## Available Assertions @@ -918,6 +922,7 @@ Laravel's `Illuminate\Testing\TestResponse` class provides a variety of custom a [assertDontSeeText](#assert-dont-see-text) [assertDownload](#assert-download) [assertExactJson](#assert-exact-json) +[assertExactJsonStructure](#assert-exact-json-structure) [assertForbidden](#assert-forbidden) [assertFound](#assert-found) [assertGone](#assert-gone) @@ -1069,6 +1074,15 @@ Assert that the response contains an exact match of the given JSON data: $response->assertExactJson(array $data); + +#### assertExactJsonStructure + +Assert that the response contains an exact match of the given JSON structure: + + $response->assertExactJsonStructure(array $data); + +This method is a more strict variant of [assertJsonStructure](#assert-json-structure). In contrast with `assertJsonStructure`, this method will fail if the response contains any keys that aren't explicitly included in the expected JSON structure. + #### assertForbidden @@ -1118,7 +1132,7 @@ Assert that the response contains the given JSON data: $response->assertJson(array $data, $strict = false); -The `assertJson` method converts the response to an array and utilizes `PHPUnit::assertArraySubset` 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. +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 @@ -1317,7 +1331,7 @@ Assert that the response has a moved permanently (301) HTTP status code: Assert that the response has the given URI value in the `Location` header: $response->assertLocation($uri); - + #### assertContent diff --git a/installation.md b/installation.md index 56c43e782a9..ad451b0d5ad 100644 --- a/installation.md +++ b/installation.md @@ -2,11 +2,16 @@ - [Meet Laravel](#meet-laravel) - [Why Laravel?](#why-laravel) -- [Creating a Laravel Project](#creating-a-laravel-project) +- [Creating a Laravel Application](#creating-a-laravel-project) + - [Installing PHP and the Laravel Installer](#installing-php) + - [Creating an Application](#creating-an-application) - [Initial Configuration](#initial-configuration) - [Environment Based Configuration](#environment-based-configuration) - [Databases and Migrations](#databases-and-migrations) - [Directory Configuration](#directory-configuration) +- [Local Installation Using Herd](#local-installation-using-herd) + - [Herd on macOS](#herd-on-macos) + - [Herd on Windows](#herd-on-windows) - [Docker Installation Using Sail](#docker-installation-using-sail) - [Sail on macOS](#sail-on-macos) - [Sail on Windows](#sail-on-windows) @@ -51,35 +56,59 @@ Need extreme scaling? Platforms like [Laravel Vapor](https://vapor.laravel.com) 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 Project +## Creating a Laravel Application -Before creating your first Laravel project, make sure that your local machine has PHP and [Composer](https://getcomposer.org) installed. If you are developing on macOS or Windows, PHP and Composer can be installed in minutes via [Laravel Herd](https://herd.laravel.com). In addition, we recommend [installing Node and NPM](https://nodejs.org). + +### Installing PHP and the Laravel Installer -After you have installed PHP and Composer, you may create a new Laravel project via Composer's `create-project` command: +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. -```nothing -composer create-project laravel/laravel example-app +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: + +```shell tab=macOS +/bin/bash -c "$(curl -fsSL https://php.new/install/mac/8.4)" +``` + +```shell tab=Windows PowerShell +# 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')) ``` -Or, you may create new Laravel projects by globally installing [the Laravel installer](https://github.com/laravel/installer) via Composer: +```shell tab=Linux +/bin/bash -c "$(curl -fsSL https://php.new/install/linux/8.4)" +``` -```nothing +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: + +```shell composer global require laravel/installer +``` + +> [!NOTE] +> For a fully-featured, graphical PHP installation and management experience, check out [Laravel Herd](#local-installation-using-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: + +```nothing laravel new example-app ``` -Once the project has been created, start Laravel's local development server using Laravel Artisan's `serve` command: +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: ```nothing cd example-app - -php artisan serve +npm install && npm run build +composer run dev ``` -Once you have started the Artisan development server, your application will be accessible in your web browser at [http://localhost:8000](http://localhost:8000). Next, you're ready to [start taking your next steps into the Laravel ecosystem](#next-steps). Of course, you may also want to [configure a database](#databases-and-migrations). +Once you have started the development server, your application will be accessible in your web browser at [http://localhost:8000](http://localhost:8000). Next, you're ready to [start taking your next steps into the Laravel ecosystem](#next-steps). Of course, you may also want to [configure a database](#databases-and-migrations). -> [!NOTE] +> [!NOTE] > If you would like a head start when developing your Laravel application, consider using one of our [starter kits](/docs/{{version}}/starter-kits). Laravel's starter kits provide backend and frontend authentication scaffolding for your new Laravel application. @@ -96,15 +125,15 @@ Since many of Laravel's configuration option values may vary depending on whethe 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. -> [!NOTE] +> [!NOTE] > For more information about the `.env` file and environment based configuration, check out the full [configuration documentation](/docs/{{version}}/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 a SQLite database. +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 project, Laravel created a `database/database.sqlite` file for you, and ran the necessary migrations to create the application's database tables. +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: @@ -123,18 +152,68 @@ If you choose to use a database other than SQLite, you will need to create the d php artisan migrate ``` -> [!NOTE] -> If you are developing on macOS and need to install MySQL, PostgreSQL, or Redis locally, consider using [DBngin](https://dbngin.com/). +> [!NOTE] +> 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). ### 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. + +## Local 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`. + +> [!NOTE] +> [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: + +```nothing +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: + +```nothing +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). + ## Docker Installation Using Sail -We want it to be as easy as possible to get started with Laravel regardless of your preferred operating system. So, there are a variety of options for developing and running a Laravel project on your local machine. While you may wish to explore these options at a later time, Laravel provides [Sail](/docs/{{version}}/sail), a built-in solution for running your Laravel project using [Docker](https://www.docker.com). +We want it to be as easy as possible to get started with Laravel regardless of your preferred operating system. So, there are a variety of options for developing and running a Laravel application on your local machine. While you may wish to explore these options at a later time, Laravel provides [Sail](/docs/{{version}}/sail), a built-in solution for running your Laravel application using [Docker](https://www.docker.com). Docker is a tool for running applications and services in small, light-weight "containers" which do not interfere with your local machine's installed software or configuration. This means you don't have to worry about configuring or setting up complicated development tools such as web servers and databases on your local machine. To get started, you only need to install [Docker Desktop](https://www.docker.com/products/docker-desktop). @@ -146,7 +225,7 @@ Laravel Sail is a light-weight command-line interface for interacting with Larav ### Sail on macOS -If you're developing on a Mac and [Docker Desktop](https://www.docker.com/products/docker-desktop) is already installed, you can use a simple terminal command to create a new Laravel project. For example, to create a new Laravel application in a directory named "example-app", you may run the following command in your terminal: +If you're developing on a Mac and [Docker Desktop](https://www.docker.com/products/docker-desktop) is already installed, you can use a simple terminal command to create a new Laravel application. For example, to create a new Laravel application in a directory named "example-app", you may run the following command in your terminal: ```shell curl -s "https://laravel.build/example-app" | bash @@ -156,7 +235,7 @@ Of course, you can change "example-app" in this URL to anything you like - just Sail installation may take several minutes while Sail's application containers are built on your local machine. -After the project has been created, you can navigate to the application directory and start Laravel Sail. Laravel Sail provides a simple command-line interface for interacting with Laravel's default Docker configuration: +After the application has been created, you can navigate to the application directory and start Laravel Sail. Laravel Sail provides a simple command-line interface for interacting with Laravel's default Docker configuration: ```shell cd example-app @@ -183,7 +262,7 @@ Before we create a new Laravel application on your Windows machine, make sure to > [!NOTE] > After installing and enabling WSL2, you should ensure that Docker Desktop is [configured to use the WSL2 backend](https://docs.docker.com/docker-for-windows/wsl/). -Next, you are ready to create your first Laravel project. Launch [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701?rtc=1&activetab=pivot:overviewtab) and begin a new terminal session for your WSL2 Linux operating system. Next, you can use a simple terminal command to create a new Laravel project. For example, to create a new Laravel application in a directory named "example-app", you may run the following command in your terminal: +Next, you are ready to create your first Laravel application. Launch [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701?rtc=1&activetab=pivot:overviewtab) and begin a new terminal session for your WSL2 Linux operating system. Next, you can use a simple terminal command to create a new Laravel application. For example, to create a new Laravel application in a directory named "example-app", you may run the following command in your terminal: ```shell curl -s https://laravel.build/example-app | bash @@ -193,7 +272,7 @@ Of course, you can change "example-app" in this URL to anything you like - just Sail installation may take several minutes while Sail's application containers are built on your local machine. -After the project has been created, you can navigate to the application directory and start Laravel Sail. Laravel Sail provides a simple command-line interface for interacting with Laravel's default Docker configuration: +After the application has been created, you can navigate to the application directory and start Laravel Sail. Laravel Sail provides a simple command-line interface for interacting with Laravel's default Docker configuration: ```shell cd example-app @@ -216,12 +295,12 @@ Finally, you can access the application in your web browser at: http://localhost Of course, you will need to be able to modify the Laravel application files that were created within your WSL2 installation. To accomplish this, we recommend using Microsoft's [Visual Studio Code](https://code.visualstudio.com) editor and their first-party extension for [Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack). -Once these tools are installed, you may open any Laravel project by executing the `code .` command from your application's root directory using Windows Terminal. +Once these tools are installed, you may open any Laravel application by executing the `code .` command from your application's root directory using Windows Terminal. ### Sail on Linux -If you're developing on Linux and [Docker Compose](https://docs.docker.com/compose/install/) is already installed, you can use a simple terminal command to create a new Laravel project. +If you're developing on Linux and [Docker Compose](https://docs.docker.com/compose/install/) is already installed, you can use a simple terminal command to create a new Laravel application. First, if you are using Docker Desktop for Linux, you should execute the following command. If you are not using Docker Desktop for Linux, you may skip this step: @@ -239,7 +318,7 @@ Of course, you can change "example-app" in this URL to anything you like - just Sail installation may take several minutes while Sail's application containers are built on your local machine. -After the project has been created, you can navigate to the application directory and start Laravel Sail. Laravel Sail provides a simple command-line interface for interacting with Laravel's default Docker configuration: +After the application has been created, you can navigate to the application directory and start Laravel Sail. Laravel Sail provides a simple command-line interface for interacting with Laravel's default Docker configuration: ```shell cd example-app @@ -261,7 +340,7 @@ Finally, you can access the application in your web browser at: http://localhost ### Choosing Your Sail Services -When creating a new Laravel application via Sail, you may use the `with` query string variable to choose which services should be configured in your new application's `docker-compose.yml` file. Available services include `mysql`, `pgsql`, `mariadb`, `redis`, `memcached`, `meilisearch`, `typesense`, `minio`, `selenium`, and `mailpit`: +When creating a new Laravel application via Sail, you may use the `with` query string variable to choose which services should be configured in your new application's `docker-compose.yml` file. Available services include `mysql`, `pgsql`, `mariadb`, `redis`, `valkey`, `memcached`, `meilisearch`, `typesense`, `minio`, `selenium`, and `mailpit`: ```shell curl -s "https://laravel.build/example-app?with=mysql,redis" | bash @@ -285,7 +364,7 @@ In addition, the community maintained [Laravel Idea](https://laravel-idea.com/) ## Next Steps -Now that you have created your Laravel project, you may be wondering what to learn next. First, we strongly recommend becoming familiar with how Laravel works by reading the following documentation: +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:
diff --git a/localization.md b/localization.md index c001b512327..c5e0313db6b 100644 --- a/localization.md +++ b/localization.md @@ -91,9 +91,9 @@ You may instruct Laravel's "pluralizer", which is used by Eloquent and other por */ public function boot(): void { - Pluralizer::useLanguage('spanish'); + Pluralizer::useLanguage('spanish'); - // ... + // ... } > [!WARNING] @@ -152,7 +152,7 @@ You may retrieve translation strings from your language files using the `__` hel If the specified translation string does not exist, the `__` function will return the translation string key. So, using the example above, the `__` function would return `messages.welcome` if the translation string does not exist. - If you are using your [default translation strings as your translation keys](#using-translation-strings-as-keys), you should pass the default translation of your string to the `__` function; +If you are using your [default translation strings as your translation keys](#using-translation-strings-as-keys), you should pass the default translation of your string to the `__` function; echo __('I love programming.'); diff --git a/logging.md b/logging.md index 2ea1ff8e682..251a52d663b 100644 --- a/logging.md +++ b/logging.md @@ -41,17 +41,17 @@ Each log channel is powered by a "driver". The driver determines how and where t
-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 +| 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. |
@@ -79,11 +79,11 @@ The `single` and `daily` channels have three optional configuration options: `bu
-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` +| 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` |
@@ -91,9 +91,9 @@ Additionally, the retention policy for the `daily` channel can be configured via
-Name | Description | Default -------------- |-------------------------------------------------------------------| ------------- -`days` | The number of days that daily log files should be retained | `7` +| Name | Description | Default | +| ------ | ----------------------------------------------------------- | ------- | +| `days` | The number of days that daily log files should be retained. | `14` |
@@ -295,7 +295,7 @@ If you would like to share contextual information across _all_ logging channels, } } -> [!NOTE] +> [!NOTE] > If you need to share log context while processing queued jobs, you may utilize [job middleware](/docs/{{version}}/queues#job-middleware). @@ -417,13 +417,12 @@ If you are using a Monolog handler that is capable of providing its own formatte 'formatter' => 'default', ], + +#### Monolog Processors - - #### 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). +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: +If you would like to customize the processors for a `monolog` driver, add a `processors` configuration value to your channel's configuration: 'memory' => [ 'driver' => 'monolog', @@ -443,7 +442,6 @@ If you are using a Monolog handler that is capable of providing its own formatte ], ], - ### Creating Custom Channels via Factories diff --git a/mail.md b/mail.md index b29c720bfd4..af1fe496b8e 100644 --- a/mail.md +++ b/mail.md @@ -36,7 +36,7 @@ ## Introduction -Sending email doesn't have to be complicated. Laravel provides a clean, simple email API powered by the popular [Symfony Mailer](https://symfony.com/doc/7.0/mailer.html) component. Laravel and Symfony Mailer provide drivers for sending email via SMTP, Mailgun, Postmark, Amazon SES, and `sendmail`, allowing you to quickly get started sending mail through a local or cloud based service of your choice. +Sending email doesn't have to be complicated. Laravel provides a clean, simple email API powered by the popular [Symfony Mailer](https://symfony.com/doc/7.0/mailer.html) component. Laravel and Symfony Mailer provide drivers for sending email via SMTP, Mailgun, Postmark, Resend, Amazon SES, and `sendmail`, allowing you to quickly get started sending mail through a local or cloud based service of your choice. ### Configuration @@ -48,7 +48,7 @@ Within your `mail` configuration file, you will find a `mailers` configuration a ### Driver / Transport Prerequisites -The API based drivers such as Mailgun, Postmark, and MailerSend are often simpler and faster than sending mail via SMTP servers. Whenever possible, we recommend that you use one of these drivers. +The API based drivers such as Mailgun, Postmark, Resend, and MailerSend are often simpler and faster than sending mail via SMTP servers. Whenever possible, we recommend that you use one of these drivers. #### Mailgun Driver @@ -89,7 +89,7 @@ If you are not using the United States [Mailgun region](https://documentation.ma #### Postmark Driver -To use the Postmark driver, install Symfony's Postmark Mailer transport via Composer: +To use the [Postmark](https://postmarkapp.com/) driver, install Symfony's Postmark Mailer transport via Composer: ```shell composer require symfony/postmark-mailer symfony/http-client @@ -113,6 +113,21 @@ If you would like to specify the Postmark message stream that should be used by 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: + +```shell +composer require resend/resend-php +``` + +Next, set the `default` option in your application's `config/mail.php` configuration file to `resend`. After configuring your application's default mailer, ensure that your `config/services.php` configuration file contains the following options: + + 'resend' => [ + 'key' => env('RESEND_KEY'), + ], + #### SES Driver @@ -180,7 +195,7 @@ 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`: -```shell +```ini MAIL_MAILER=mailersend MAIL_FROM_ADDRESS=app@yourdomain.com MAIL_FROM_NAME="App Name" @@ -674,7 +689,7 @@ Some third-party email providers such as Mailgun and Postmark support message "t ); } -If your application is using the Mailgun driver, you may consult Mailgun's documentation for more information on [tags](https://documentation.mailgun.com/en/latest/user_manual.html#tagging-1) and [metadata](https://documentation.mailgun.com/en/latest/user_manual.html#attaching-data-to-messages). Likewise, the Postmark documentation may also be consulted for more information on their support for [tags](https://postmarkapp.com/blog/tags-support-for-smtp) and [metadata](https://postmarkapp.com/support/article/1125-custom-metadata-faq). +If your application is using the Mailgun driver, you may consult Mailgun's documentation for more information on [tags](https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages/#tagging) and [metadata](https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages/#attaching-data-to-messages). Likewise, the Postmark documentation may also be consulted for more information on their support for [tags](https://postmarkapp.com/blog/tags-support-for-smtp) and [metadata](https://postmarkapp.com/support/article/1125-custom-metadata-faq). 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. @@ -685,7 +700,7 @@ Laravel's mail capabilities are powered by Symfony Mailer. Laravel allows you to use Illuminate\Mail\Mailables\Envelope; use Symfony\Component\Mime\Email; - + /** * Get the message envelope. */ @@ -784,10 +799,10 @@ The table component allows you to transform a Markdown table into an HTML table. ```blade -| Laravel | Table | Example | -| ------------- |:-------------:| --------:| -| Col 2 is | Centered | $10 | -| Col 3 is | Right-Aligned | $20 | +| Laravel | Table | Example | +| ------------- | :-----------: | ------------: | +| Col 2 is | Centered | $10 | +| Col 3 is | Right-Aligned | $20 | ``` @@ -1118,6 +1133,12 @@ test('orders can be shipped', function () { // Assert a mailable was sent twice... Mail::assertSent(OrderShipped::class, 2); + // Assert a mailable was sent to an email address... + Mail::assertSent(OrderShipped::class, 'example@laravel.com'); + + // Assert a mailable was sent to multiple email addresses... + Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']); + // Assert a mailable was not sent... Mail::assertNotSent(AnotherMailable::class); @@ -1152,6 +1173,12 @@ class ExampleTest extends TestCase // Assert a mailable was sent twice... Mail::assertSent(OrderShipped::class, 2); + // Assert a mailable was sent to an email address... + Mail::assertSent(OrderShipped::class, 'example@laravel.com'); + + // Assert a mailable was sent to multiple email addresses... + Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']); + // Assert a mailable was not sent... Mail::assertNotSent(AnotherMailable::class); diff --git a/middleware.md b/middleware.md index a99d965b9a0..95db7bb8b7d 100644 --- a/middleware.md +++ b/middleware.md @@ -27,7 +27,7 @@ To create a new middleware, use the `make:middleware` Artisan command: 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: +This command will place a new `EnsureTokenIsValid` class within your `app/Http/Middleware` directory. In this middleware, we will only allow access to the route if the supplied `token` input matches a specified value. Otherwise, we will redirect the users back to the `/home` URI: input('token') !== 'my-secret-token') { - return redirect('home'); + return redirect('/home'); } return $next($request); @@ -61,7 +61,6 @@ It's best to envision middleware as a series of "layers" HTTP requests must pass > [!NOTE] > All middleware are resolved via the [service container](/docs/{{version}}/container), so you may type-hint any dependencies you need within a middleware's constructor. - #### Middleware and Responses @@ -130,6 +129,7 @@ If you would like to manage Laravel's global middleware stack manually, you may ->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, @@ -140,7 +140,6 @@ If you would like to manage Laravel's global middleware stack manually, you may ]); }) - ### Assigning Middleware to Routes @@ -222,18 +221,26 @@ Middleware groups may be assigned to routes and controller actions using the sam 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 `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` +
+ +| 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: @@ -294,7 +301,7 @@ If you would like to manually manage all of the middleware within Laravel's defa ### Middleware Aliases -You may assign aliases to middleware in your application's `bootstrap/app.php` file. Middleware aliases allows you to define a short alias for a given middleware class, which can be especially useful for middleware with long class names: +You may assign aliases to middleware in your application's `bootstrap/app.php` file. Middleware aliases allow you to define a short alias for a given middleware class, which can be especially useful for middleware with long class names: use App\Http\Middleware\EnsureUserIsSubscribed; @@ -312,20 +319,24 @@ Once the middleware alias has been defined in your application's `bootstrap/app. 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` +
+ +| 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 @@ -384,15 +395,17 @@ Additional middleware parameters will be passed to the middleware after the `$ne Middleware parameters may be specified when defining the route by separating the middleware name and parameters with a `:`: + use App\Http\Middleware\EnsureUserHasRole; + Route::put('/post/{id}', function (string $id) { // ... - })->middleware('role:editor'); + })->middleware(EnsureUserHasRole::class.':editor'); Multiple parameters may be delimited by commas: Route::put('/post/{id}', function (string $id) { // ... - })->middleware('role:editor,publisher'); + })->middleware(EnsureUserHasRole::class.':editor,publisher'); ## Terminable Middleware diff --git a/migrations.md b/migrations.md index 6df04a5d488..a0be8bed27a 100644 --- a/migrations.md +++ b/migrations.md @@ -71,7 +71,7 @@ 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. > [!WARNING] -> Migration squashing is only available for the MySQL, PostgreSQL, and SQLite databases and utilizes the database's command-line client. +> Migration squashing is only available for the MariaDB, MySQL, PostgreSQL, and SQLite databases and utilizes the database's command-line client. ## Migration Structure @@ -191,7 +191,7 @@ php artisan migrate:rollback --step=5 You may roll back a specific "batch" of migrations by providing the `batch` option to the `rollback` command, where the `batch` option corresponds to a batch value within your application's `migrations` database table. For example, the following command will roll back all migrations in batch three: ```shell - php 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: @@ -290,7 +290,7 @@ If you want to perform a schema operation on a database connection that is not y $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 MySQL: +In addition, a few other properties and methods may be used to define other aspects of the table's creation. The `engine` property may be used to specify the table's storage engine when using MariaDB or MySQL: Schema::create('users', function (Blueprint $table) { $table->engine('InnoDB'); @@ -298,7 +298,7 @@ In addition, a few other properties and methods may be used to define other aspe // ... }); -The `charset` and `collation` properties may be used to specify the character set and collation for the created table when using MySQL: +The `charset` and `collation` properties may be used to specify the character set and collation for the created table when using MariaDB or MySQL: Schema::create('users', function (Blueprint $table) { $table->charset('utf8mb4'); @@ -315,7 +315,7 @@ The `temporary` method may be used to indicate that the table should be "tempora // ... }); -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 MySQL and PostgreSQL: +If you would like to add a "comment" to a database table, you may invoke the `comment` method on the table instance. Table comments are currently only supported by MariaDB, MySQL, and PostgreSQL: Schema::create('calculations', function (Blueprint $table) { $table->comment('Business calculations'); @@ -458,6 +458,7 @@ The schema builder blueprint offers a variety of methods that correspond to the [uuidMorphs](#column-method-uuidMorphs) [ulid](#column-method-ulid) [uuid](#column-method-uuid) +[vector](#column-method-vector) [year](#column-method-year)
@@ -627,7 +628,7 @@ The `integer` method creates an `INTEGER` equivalent column: The `ipAddress` method creates a `VARCHAR` equivalent column: $table->ipAddress('visitor'); - + When using PostgreSQL, an `INET` column will be created. @@ -918,6 +919,13 @@ The `uuid` method creates a `UUID` equivalent column: $table->uuid('id'); + +#### `vector()` {.collection-method} + +The `vector` method creates a `vector` equivalent column: + + $table->vector('embedding', dimensions: 100); + #### `year()` {.collection-method} @@ -939,25 +947,29 @@ In addition to the column types listed above, there are several column "modifier The following table contains all of the available column modifiers. This list does not include [index modifiers](#creating-indexes): -Modifier | Description --------- | ----------- -`->after('column')` | Place the column "after" another column (MySQL). -`->autoIncrement()` | Set INTEGER columns as auto-incrementing (primary key). -`->charset('utf8mb4')` | Specify a character set for the column (MySQL). -`->collation('utf8mb4_unicode_ci')` | Specify a collation for the column. -`->comment('my comment')` | Add a comment to a column (MySQL / PostgreSQL). -`->default($value)` | Specify a "default" value for the column. -`->first()` | Place the column "first" in the table (MySQL). -`->from($integer)` | Set the starting value of an auto-incrementing field (MySQL / PostgreSQL). -`->invisible()` | Make the column "invisible" to `SELECT *` queries (MySQL). -`->nullable($value = true)` | Allow NULL values to be inserted into the column. -`->storedAs($expression)` | Create a stored generated column (MySQL / PostgreSQL / SQLite). -`->unsigned()` | Set INTEGER columns as UNSIGNED (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 (MySQL). -`->virtualAs($expression)` | Create a virtual generated column (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). +
+ +| 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 @@ -992,7 +1004,7 @@ The `default` modifier accepts a value or an `Illuminate\Database\Query\Expressi #### Column Order -When using the MySQL database, the `after` method may be used to add columns after an existing column in the schema: +When using the MariaDB or MySQL database, the `after` method may be used to add columns after an existing column in the schema: $table->after('password', function (Blueprint $table) { $table->string('address_line1'); @@ -1054,14 +1066,18 @@ You may drop multiple columns from a table by passing an array of column names t 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. +
+ +| 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 @@ -1095,15 +1111,19 @@ When creating an index, Laravel will automatically generate an index name based 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 (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). +
+ +| 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 @@ -1117,13 +1137,17 @@ To rename an index, you may use the `renameIndex` method provided by the schema 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). +
+ +| 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: @@ -1168,14 +1192,20 @@ You may also specify the desired action for the "on delete" and "on update" prop 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](#column-modifiers) must be called before the `constrained` method: @@ -1208,18 +1238,23 @@ You may enable or disable foreign key constraints within your migrations by usin }); > [!WARNING] -> SQLite disables foreign key constraints by default. When using SQLite, make sure to [enable foreign key support](/docs/{{version}}/database#configuration) in your database configuration before attempting to create them in your migrations. In addition, SQLite only supports foreign keys upon creation of the table and [not when tables are altered](https://www.sqlite.org/omitted.html). +> SQLite disables foreign key constraints by default. When using SQLite, make sure to [enable foreign key support](/docs/{{version}}/database#configuration) in your database configuration before attempting to create them in your migrations. ## Events For convenience, each migration operation will dispatch an [event](/docs/{{version}}/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\SchemaDumped` | A database schema dump has completed. | -| `Illuminate\Database\Events\SchemaLoaded` | An existing database schema dump has loaded. | +
+ +| 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/mocking.md b/mocking.md index 550da118b19..186e29f6bdf 100644 --- a/mocking.md +++ b/mocking.md @@ -240,7 +240,7 @@ You may also provide a closure to the various time travel methods. The closure w $this->travel(5)->days(function () { // Test something five days into the future... }); - + $this->travelTo(now()->subDays(10), function () { // Test something during a given moment... }); diff --git a/mongodb.md b/mongodb.md new file mode 100644 index 00000000000..af992cd0d24 --- /dev/null +++ b/mongodb.md @@ -0,0 +1,99 @@ +# MongoDB + +- [Introduction](#introduction) +- [Installation](#installation) + - [MongoDB Driver](#mongodb-driver) + - [Starting a MongoDB Server](#starting-a-mongodb-server) + - [Install the Laravel MongoDB Package](#install-the-laravel-mongodb-package) +- [Configuration](#configuration) +- [Features](#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: + +```shell +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: + +```shell +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: + +```ini +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: + +```ini +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: + +```shell +composer require mongodb/laravel-mongodb +``` + +> [!NOTE] +> 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: + +```php +'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/gridfs/), 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/notifications.md b/notifications.md index 84dddd24c15..17235144293 100644 --- a/notifications.md +++ b/notifications.md @@ -169,9 +169,6 @@ If you would like to delay the delivery of the notification, you may chain the ` $user->notify((new InvoicePaid($invoice))->delay($delay)); - -#### Delaying Notifications per Channel - You may pass an array to the `delay` method to specify the delay amount for specific channels: $user->notify((new InvoicePaid($invoice))->delay([ @@ -253,6 +250,27 @@ If you would like to specify a specific queue that should be used for each notif ]; } + +#### Queued Notification Middleware + +Queued notifications may define middleware [just like queued jobs](/docs/{{version}}/queues#job-middleware). To get started, define a `middleware` method on your notification class. The `middleware` method will receive `$notifiable` and `$channel` variables, which allow you to customize the returned middleware based on the notification's destination: + + use Illuminate\Queue\Middleware\RateLimited; + + /** + * Get the middleware the notification job should pass through. + * + * @return array + */ + public function middleware(object $notifiable, string $channel) + { + return match ($channel) { + 'email' => [new RateLimited('postmark')], + 'slack' => [new RateLimited('slack')], + default => [], + }; + } + #### Queued Notifications and Database Transactions @@ -762,10 +780,10 @@ The table component allows you to transform a Markdown table into an HTML table. ```blade -| Laravel | Table | Example | -| ------------- |:-------------:| --------:| -| Col 2 is | Centered | $10 | -| Col 3 is | Right-Aligned | $20 | +| Laravel | Table | Example | +| ------------- | :-----------: | ------------: | +| Col 2 is | Centered | $10 | +| Col 3 is | Right-Aligned | $20 | ``` @@ -1108,7 +1126,7 @@ 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. +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. If you want to send messages as your Slack App, you should ensure that your App also has the `chat:write:bot` scope. 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: @@ -1156,6 +1174,44 @@ If a notification supports being sent as a Slack message, you should define a `t }); } + +#### Using Slack's Block Kit Builder Template + +Instead of using the fluent message builder methods to construct your Block Kit message, you may provide the raw JSON payload generated by Slack's Block Kit Builder to the `usingBlockKitTemplate` method: + + use Illuminate\Notifications\Slack\SlackMessage; + use Illuminate\Support\Str; + + /** + * Get the Slack representation of the notification. + */ + public function toSlack(object $notifiable): SlackMessage + { + $template = <<usingBlockKitTemplate($template); + } + ### Slack Interactivity @@ -1249,7 +1305,7 @@ To direct Slack notifications to the appropriate Slack team and channel, define - 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: - + ### FrankenPHP -> [!WARNING] -> FrankenPHP's Octane integration is in beta and should be used with caution in production. - -[FrankenPHP](https://frankenphp.dev) is a PHP application server, written in Go, that supports modern web features like early hints and Zstandard compression. When you install Octane and choose FrankenPHP as your server, Octane will automatically download and install the FrankenPHP binary for you. +[FrankenPHP](https://frankenphp.dev) is a PHP application server, written in Go, that supports modern web features like early hints, Brotli, and Zstandard compression. When you install Octane and choose FrankenPHP as your server, Octane will automatically download and install the FrankenPHP binary for you. #### FrankenPHP via Laravel Sail @@ -81,7 +78,7 @@ Finally, add a `SUPERVISOR_PHP_COMMAND` environment variable to the `laravel.tes services: laravel.test: environment: - SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port=80" # [tl! add] + SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port='${APP_PORT:-80}'" # [tl! add] XDG_CONFIG_HOME: /var/www/html/config # [tl! add] XDG_DATA_HOME: /var/www/html/data # [tl! add] ``` @@ -107,7 +104,7 @@ Typically, you should access your FrankenPHP Sail application via `https://local #### FrankenPHP via Docker -Using FrankenPHP's official Docker images can offer improved performance and the use additional extensions not included with static installations of FrankenPHP. In addition, the official Docker images provide support for running FrankenPHP on platforms it doesn't natively support, such as Windows. FrankenPHP's official Docker images are suitable for both local development and production usage. +Using FrankenPHP's official Docker images can offer improved performance and the use of additional extensions not included with static installations of FrankenPHP. In addition, the official Docker images provide support for running FrankenPHP on platforms it doesn't natively support, such as Windows. FrankenPHP's official Docker images are suitable for both local development and production usage. You may use the following Dockerfile as a starting point for containerizing your FrankenPHP powered Laravel application: @@ -131,13 +128,15 @@ services: frankenphp: build: context: . - entrypoint: php artisan octane:frankenphp --max-requests=1 + entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1 ports: - "8000:8000" volumes: - .:/app ``` +If the `--log-level` option is explicitly passed to the `php artisan octane:start` command, Octane will use FrankenPHP's native logger and, unless configured differently, will produce structured JSON logs. + You may consult [the official FrankenPHP documentation](https://frankenphp.dev/docs/docker/) for more information on running FrankenPHP with Docker. @@ -153,7 +152,7 @@ If you plan to develop your application using [Laravel Sail](/docs/{{version}}/s ```shell ./vendor/bin/sail up -./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http +./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http ``` Next, you should start a Sail shell and use the `rr` executable to retrieve the latest Linux based build of the RoadRunner binary: @@ -171,7 +170,7 @@ Then, add a `SUPERVISOR_PHP_COMMAND` environment variable to the `laravel.test` services: laravel.test: environment: - SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port=80" # [tl! add] + SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port='${APP_PORT:-80}'" # [tl! add] ``` Finally, ensure the `rr` binary is executable and build your Sail images: @@ -216,7 +215,7 @@ To get started, add a `SUPERVISOR_PHP_COMMAND` environment variable to the `lara services: laravel.test: environment: - SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port=80" # [tl! add] + SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port='${APP_PORT:-80}'" # [tl! add] ``` Finally, build your Sail images: diff --git a/packages.md b/packages.md index 4999c4e343d..0ee98e13d8b 100644 --- a/packages.md +++ b/packages.md @@ -13,6 +13,7 @@ - [View Components](#view-components) - ["About" Artisan Command](#about-artisan-command) - [Commands](#commands) + - [Optimize Commands](#optimize-commands) - [Public Assets](#public-assets) - [Publishing File Groups](#publishing-file-groups) @@ -107,7 +108,7 @@ Now, when users of your package execute Laravel's `vendor:publish` command, your $value = config('courier.option'); > [!WARNING] -> You should not define closures in your configuration files. They can not be serialized correctly when users execute the `config:cache` Artisan command. +> 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 @@ -339,6 +340,24 @@ To register your package's Artisan commands with Laravel, you may use the `comma } } + +### Optimize Commands + +Laravel's [`optimize` command](/docs/{{version}}/deployment#optimization) caches the application's configuration, events, routes, and views. Using the `optimizes` method, you may register your package's own Artisan commands that should be invoked when the `optimize` and `optimize:clear` commands are executed: + + /** + * Bootstrap any package services. + */ + public function boot(): void + { + if ($this->app->runningInConsole()) { + $this->optimizes( + optimize: 'package:optimize', + clear: 'package:clear-optimizations', + ); + } + } + ## Public Assets diff --git a/pagination.md b/pagination.md index d55a67fa6c2..d6179e599ab 100644 --- a/pagination.md +++ b/pagination.md @@ -150,7 +150,7 @@ 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 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. @@ -332,48 +332,56 @@ Laravel includes pagination views built using [Bootstrap CSS](https://getbootstr Each paginator instance provides additional pagination information via the following methods: -Method | Description -------- | ----------- -`$paginator->count()` | 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->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. +
+ +| Method | Description | +| --- | --- | +| `$paginator->count()` | 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->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. +
+ +| 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/passport.md b/passport.md index 315d906bbbf..9aaa57cb908 100644 --- a/passport.md +++ b/passport.md @@ -438,7 +438,7 @@ This `/oauth/token` route will return a JSON response containing `access_token`, #### JSON API -Passport also includes a JSON API for managing authorized access tokens. You may pair this with your own frontend to offer your users a dashboard for managing access tokens. For convenience, we'll use [Axios](https://github.com/mzabriskie/axios) to demonstrate making HTTP requests to the endpoints. The JSON API is guarded by the `web` and `auth` middleware; therefore, it may only be called from your own application. +Passport also includes a JSON API for managing authorized access tokens. You may pair this with your own frontend to offer your users a dashboard for managing access tokens. For convenience, we'll use [Axios](https://github.com/axios/axios) to demonstrate making HTTP requests to the endpoints. The JSON API is guarded by the `web` and `auth` middleware; therefore, it may only be called from your own application. #### `GET /oauth/tokens` @@ -518,7 +518,7 @@ php artisan passport:purge --expired You may also configure a [scheduled job](/docs/{{version}}/scheduling) in your application's `routes/console.php` file to automatically prune your tokens on a schedule: - use Laravel\Support\Facades\Schedule; + use Illuminate\Support\Facades\Schedule; Schedule::command('passport:purge')->hourly(); @@ -867,7 +867,7 @@ Once you have created a personal access client, you may issue tokens for a given #### JSON API -Passport also includes a JSON API for managing personal access tokens. You may pair this with your own frontend to offer your users a dashboard for managing personal access tokens. Below, we'll review all of the API endpoints for managing personal access tokens. For convenience, we'll use [Axios](https://github.com/mzabriskie/axios) to demonstrate making HTTP requests to the endpoints. +Passport also includes a JSON API for managing personal access tokens. You may pair this with your own frontend to offer your users a dashboard for managing personal access tokens. Below, we'll review all of the API endpoints for managing personal access tokens. For convenience, we'll use [Axios](https://github.com/axios/axios) to demonstrate making HTTP requests to the endpoints. The JSON API is guarded by the `web` and `auth` middleware; therefore, it may only be called from your own application. It is not able to be called from an external source. @@ -1162,10 +1162,14 @@ When using this method of authentication, you will need to ensure a valid CSRF t Passport raises events when issuing access tokens and refresh tokens. You may [listen for these events](/docs/{{version}}/events) to prune or revoke other access tokens in your database: -Event Name | -------------- | -`Laravel\Passport\Events\AccessTokenCreated` | -`Laravel\Passport\Events\RefreshTokenCreated` | +
+ +| Event Name | +| --- | +| `Laravel\Passport\Events\AccessTokenCreated` | +| `Laravel\Passport\Events\RefreshTokenCreated` | + +
## Testing diff --git a/pennant.md b/pennant.md index 1669713113b..c6ab436696f 100644 --- a/pennant.md +++ b/pennant.md @@ -10,6 +10,7 @@ - [The `HasFeatures` Trait](#the-has-features-trait) - [Blade Directive](#blade-directive) - [Middleware](#middleware) + - [Intercepting Feature Checks](#intercepting-feature-checks) - [In-Memory Cache](#in-memory-cache) - [Scope](#scope) - [Specifying the Scope](#specifying-the-scope) @@ -27,6 +28,7 @@ - [Adding Custom Pennant Drivers](#adding-custom-pennant-drivers) - [Implementing the Driver](#implementing-the-driver) - [Registering the Driver](#registering-the-driver) + - [Defining Features Externally](#defining-features-externally) - [Events](#events) @@ -123,6 +125,7 @@ When writing a feature class, you only need to define a `resolve` method, which namespace App\Features; +use App\Models\User; use Illuminate\Support\Lottery; class NewApi @@ -141,7 +144,16 @@ class NewApi } ``` -> [!NOTE] Feature classes are resolved via the [container](/docs/{{version}}/container), so you may inject dependencies into the feature class's constructor when needed. +If you would like to manually resolve an instance of a class based feature, you may invoke the `instance` method on the `Feature` facade: + +```php +use Illuminate\Support\Facades\Feature; + +$instance = Feature::instance(NewApi::class); +``` + +> [!NOTE] +> Feature classes are resolved via the [container](/docs/{{version}}/container), so you may inject dependencies into the feature class's constructor when needed. #### Customizing the Stored Feature Name @@ -353,7 +365,7 @@ $user->features()->unless('new-api', ### Blade Directive -To make checking features in Blade a seamless experience, Pennant offers a `@feature` directive: +To make checking features in Blade a seamless experience, Pennant offers the `@feature` and `@featureany` directive: ```blade @feature('site-redesign') @@ -361,6 +373,10 @@ To make checking features in Blade a seamless experience, Pennant offers a `@fea @else @endfeature + +@featureany(['site-redesign', 'beta']) + +@endfeatureany ``` @@ -402,6 +418,78 @@ public function boot(): void } ``` + +### 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](#class-based-features) `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: + +```php +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: + +```php +isInternalTeamMember(); + } + + if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) { + return true; + } + } + + // ... +} +``` + ### In-Memory Cache @@ -450,7 +538,7 @@ You will notice that the closure we have defined is not expecting a `User`, but ```php if (Feature::for($user->team)->active('billing-v2')) { - return redirect()->to('/billing/v2'); + return redirect('/billing/v2'); } // ... @@ -605,7 +693,8 @@ Pennant's included Blade directive also makes it easy to conditionally render co @endfeature ``` -> [!NOTE] When using rich values, it is important to know that a feature is considered "active" when it has any value other than `false`. +> [!NOTE] +> 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`](#conditional-execution) method, the feature's rich value will be provided to the first closure: @@ -723,6 +812,12 @@ Feature::for($users)->loadMissing([ ]); ``` +You may load all defined features using the `loadAll` method: + +```php +Feature::for($users)->loadAll(); +``` + ## Updating Values @@ -773,7 +868,8 @@ Alternatively, you may deactivate the feature for all users: Feature::deactivateForEveryone('new-api'); ``` -> [!NOTE] 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. +> [!NOTE] +> 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 @@ -927,7 +1023,7 @@ class RedisFeatureDriver implements Driver Now, we just need to implement each of these methods using a Redis connection. For an example of how to implement each of these methods, take a look at the `Laravel\Pennant\Drivers\DatabaseDriver` in the [Pennant source code](https://github.com/laravel/pennant/blob/1.x/src/Drivers/DatabaseDriver.php) -> [!NOTE] +> [!NOTE] > Laravel does not ship with a directory to contain your extensions. You are free to place them anywhere you like. In this example, we have created an `Extensions` directory to house the `RedisFeatureDriver`. @@ -980,6 +1076,32 @@ Once the driver has been registered, you may use the `redis` driver in your appl ], + +### 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: + +```php + ## Events diff --git a/pint.md b/pint.md index 50d6719a97a..ea7f8132122 100644 --- a/pint.md +++ b/pint.md @@ -7,6 +7,8 @@ - [Presets](#presets) - [Rules](#rules) - [Excluding Files / Folders](#excluding-files-or-folders) +- [Continuous Integration](#continuous-integration) + - [GitHub Actions](#running-tests-on-github-actions) ## Introduction @@ -47,7 +49,7 @@ Pint will display a thorough list of all of the files that it updates. You can 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: +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: ```shell ./vendor/bin/pint --test @@ -59,6 +61,12 @@ If you would like Pint to only modify the files that have uncommitted changes ac ./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: + +```shell +./vendor/bin/pint --repair +``` + ## Configuring Pint @@ -73,16 +81,16 @@ As previously mentioned, Pint does not require any configuration. However, if yo In addition, if you wish to use a `pint.json` from a specific directory, you may provide the `--config` option when invoking Pint: ```shell -pint --config vendor/my-company/coding-style/pint.json +./vendor/bin/pint --config vendor/my-company/coding-style/pint.json ``` ### Presets -Presets defines 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: +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: ```shell -pint --preset psr12 +./vendor/bin/pint --preset psr12 ``` If you wish, you may also set the preset in your project's `pint.json` file: @@ -93,24 +101,24 @@ If you wish, you may also set the preset in your project's `pint.json` file: } ``` -Pint's currently supported presets are: `laravel`, `per`, `psr12`, and `symfony`. +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: +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: ```json { "preset": "laravel", "rules": { "simplified_null_return": true, - "braces": false, - "new_with_braces": { - "anonymous_class": false, - "named_class": false + "array_indentation": false, + "new_with_parentheses": { + "anonymous_class": true, + "named_class": true } } } @@ -150,3 +158,45 @@ If you would like to exclude a file by providing an exact path to the file, you ] } ``` + + +## 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: + +```yaml +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/precognition.md b/precognition.md index 3877cd0993e..a3942e15135 100644 --- a/precognition.md +++ b/precognition.md @@ -150,6 +150,20 @@ If you are validating a subset of a form's inputs with Precognition, it can be u > ``` +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 mark the fields you wish to validate as "touched" by passing their names to the `touch` method. Then, call the `validate` method with `onSuccess` or `onValidationError` callbacks: + +```html + +``` + 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: ```js @@ -247,7 +261,7 @@ export default function Form() { return (
- + {form.invalid('name') &&
{form.errors.name}
} - + + onChange={(e) => { 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 mark the fields you wish to validate as "touched" by passing their names to the `touch` method. Then, call the `validate` method with `onSuccess` or `onValidationError` callbacks: + +```jsx + +``` + 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: ```js @@ -501,6 +529,20 @@ You may also determine if an input has passed or failed validation by passing th > [!WARNING] > 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 mark the fields you wish to validate as "touched" by passing their names to the `touch` method. Then, call the `validate` method with `onSuccess` or `onValidationError` callbacks: + +```html + +``` + You may determine if a form submission request is in-flight by inspecting the form's `processing` property: ```html @@ -526,7 +568,7 @@ In the user creation example discussed above, we are using Precognition to perfo 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: ```html - ## Concurrent Processes diff --git a/prompts.md b/prompts.md index 726cd69686d..441f5c6a1b6 100644 --- a/prompts.md +++ b/prompts.md @@ -13,11 +13,13 @@ - [Search](#search) - [Multi-search](#multisearch) - [Pause](#pause) +- [Transforming Input Before Validation](#transforming-input-before-validation) - [Forms](#forms) - [Informational Messages](#informational-messages) - [Tables](#tables) - [Spin](#spin) - [Progress Bar](#progress) +- [Clearing the Terminal](#clear) - [Terminal Considerations](#terminal-considerations) - [Unsupported Environments and Fallbacks](#fallbacks) @@ -113,7 +115,7 @@ Alternatively, you may leverage the power of Laravel's [validator](/docs/{{versi ```php $name = text( label: 'What is your name?', - validate: ['name' => 'required|max:255|unique:users,name'] + validate: ['name' => 'required|max:255|unique:users'] ); ``` @@ -307,8 +309,8 @@ If you need the user to select from a predefined set of choices, you may use the use function Laravel\Prompts\select; $role = select( - 'What role should the user have?', - ['Member', 'Contributor', 'Owner'], + label: 'What role should the user have?', + options: ['Member', 'Contributor', 'Owner'] ); ``` @@ -331,7 +333,7 @@ $role = select( options: [ 'member' => 'Member', 'contributor' => 'Contributor', - 'owner' => 'Owner' + 'owner' => 'Owner', ], default: 'owner' ); @@ -348,7 +350,7 @@ $role = select( ``` -#### Validation +#### 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: @@ -358,7 +360,7 @@ $role = select( options: [ 'member' => 'Member', 'contributor' => 'Contributor', - 'owner' => 'Owner' + 'owner' => 'Owner', ], validate: fn (string $value) => $value === 'owner' && User::where('role', 'owner')->exists() @@ -372,14 +374,14 @@ If the `options` argument is an associative array, then the closure will receive ### Multi-select -If you need to the user to be able to select multiple options, you may use the `multiselect` function: +If you need the user to be able to select multiple options, you may use the `multiselect` function: ```php use function Laravel\Prompts\multiselect; $permissions = multiselect( - 'What permissions should be assigned?', - ['Read', 'Create', 'Update', 'Delete'] + label: 'What permissions should be assigned?', + options: ['Read', 'Create', 'Update', 'Delete'] ); ``` @@ -398,14 +400,14 @@ $permissions = multiselect( You may also pass an associative array to the `options` argument to return the selected options' keys instead of their values: -``` +```php $permissions = multiselect( label: 'What permissions should be assigned?', options: [ 'read' => 'Read', 'create' => 'Create', 'update' => 'Update', - 'delete' => 'Delete' + 'delete' => 'Delete', ], default: ['read', 'create'] ); @@ -430,7 +432,7 @@ By default, the user may select zero or more options. You may pass the `required $categories = multiselect( label: 'What categories should be assigned?', options: Category::pluck('name', 'id'), - required: true, + required: true ); ``` @@ -440,23 +442,23 @@ If you would like to customize the validation message, you may provide a string $categories = multiselect( label: 'What categories should be assigned?', options: Category::pluck('name', 'id'), - required: 'You must select at least one category', + required: 'You must select at least one category' ); ``` -#### Validation +#### Additional Validation You may pass a closure to the `validate` argument if you need to present an option but prevent it from being selected: -``` +```php $permissions = multiselect( label: 'What permissions should the user have?', options: [ 'read' => 'Read', 'create' => 'Create', 'update' => 'Update', - 'delete' => 'Delete' + 'delete' => 'Delete', ], validate: fn (array $values) => ! in_array('read', $values) ? 'All users require the read permission.' @@ -481,8 +483,8 @@ Alternatively, you may pass a closure as the second argument to the `suggest` fu ```php $name = suggest( - 'What is your name?', - fn ($value) => collect(['Taylor', 'Dayle']) + label: 'What is your name?', + options: fn ($value) => collect(['Taylor', 'Dayle']) ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) ) ``` @@ -560,15 +562,29 @@ If you have a lot of options for the user to select from, the `search` function use function Laravel\Prompts\search; $id = search( - 'Search for the user that should receive the mail', - fn (string $value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + 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: + +```php +$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: ```php @@ -576,7 +592,7 @@ $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::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() : [], hint: 'The user will receive an email immediately.' ); @@ -588,14 +604,14 @@ Up to five options will be displayed before the list begins to scroll. You may c $id = search( label: 'Search for the user that should receive the mail', options: fn (string $value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() : [], scroll: 10 ); ``` -#### Validation +#### Additional Validation If you would like to perform additional validation logic, you may pass a closure to the `validate` argument: @@ -603,7 +619,7 @@ If you would like to perform additional validation logic, you may pass a closure $id = search( label: 'Search for the user that should receive the mail', options: fn (string $value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() : [], validate: function (int|string $value) { $user = User::findOrFail($value); @@ -628,13 +644,27 @@ use function Laravel\Prompts\multisearch; $ids = multisearch( 'Search for the users that should receive the mail', fn (string $value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + ? 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: + +```php +$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: ```php @@ -642,7 +672,7 @@ $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::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() : [], hint: 'The user will receive an email immediately.' ); @@ -654,7 +684,7 @@ Up to five options will be displayed before the list begins to scroll. You may c $ids = multisearch( label: 'Search for the users that should receive the mail', options: fn (string $value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() : [], scroll: 10 ); @@ -667,11 +697,11 @@ By default, the user may select zero or more options. You may pass the `required ```php $ids = multisearch( - 'Search for the users that should receive the mail', - fn (string $value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + 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, + required: true ); ``` @@ -679,16 +709,16 @@ If you would like to customize the validation message, you may also provide a st ```php $ids = multisearch( - 'Search for the users that should receive the mail', - fn (string $value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + 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.' ); ``` -#### Validation +#### Additional Validation If you would like to perform additional validation logic, you may pass a closure to the `validate` argument: @@ -696,10 +726,10 @@ If you would like to perform additional validation logic, you may pass a closure $ids = multisearch( label: 'Search for the users that should receive the mail', options: fn (string $value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() : [], validate: function (array $values) { - $optedOut = User::where('name', 'like', '%a%')->findMany($values); + $optedOut = User::whereLike('name', '%a%')->findMany($values); if ($optedOut->isNotEmpty()) { return $optedOut->pluck('name')->join(', ', ', and ').' have opted out.'; @@ -721,6 +751,23 @@ 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: + +```php +$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 @@ -745,16 +792,16 @@ use function Laravel\Prompts\form; $responses = form() ->text('What is your name?', required: true, name: 'name') ->password( - 'What is your password?', + label: 'What is your password?', validate: ['password' => 'min:8'], - name: 'password', + name: 'password' ) ->confirm('Do you accept the terms?') ->submit(); User::create([ 'name' => $responses['name'], - 'password' => $responses['password'] + 'password' => $responses['password'], ]); ``` @@ -796,8 +843,8 @@ The `table` function makes it easy to display multiple rows and columns of data. use function Laravel\Prompts\table; table( - ['Name', 'Email'], - User::all(['name', 'email']) + headers: ['Name', 'Email'], + rows: User::all(['name', 'email'])->toArray() ); ``` @@ -810,8 +857,8 @@ The `spin` function displays a spinner along with an optional message while exec use function Laravel\Prompts\spin; $response = spin( - fn () => Http::get('http://example.com'), - 'Fetching response...' + message: 'Fetching response...', + callback: fn () => Http::get('http://example.com') ); ``` @@ -829,13 +876,13 @@ use function Laravel\Prompts\progress; $users = progress( label: 'Updating users', steps: User::all(), - callback: fn ($user) => $this->performTask($user), + 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: +The callback may also accept the `Laravel\Prompts\Progress` instance, allowing you to modify the label and hint on each iteration: ```php $users = progress( @@ -848,7 +895,7 @@ $users = progress( return $this->performTask($user); }, - hint: 'This may take some time.', + hint: 'This may take some time.' ); ``` @@ -870,6 +917,17 @@ foreach ($users as $user) { $progress->finish(); ``` + +## Clearing the Terminal + +The `clear` function may be used to clear the user's terminal: + +``` +use function Laravel\Prompts\clear; + +clear(); +``` + ## Terminal Considerations @@ -920,7 +978,9 @@ 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.'); + throw new \RuntimeException( + is_string($prompt->required) ? $prompt->required : 'Required.' + ); } if ($prompt->validate) { diff --git a/providers.md b/providers.md index e17d9c15635..f68718dfb67 100644 --- a/providers.md +++ b/providers.md @@ -26,7 +26,7 @@ All user-defined service providers are registered in the `bootstrap/providers.ph 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/{{version}}/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: +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: ```shell php artisan make:provider RiakServiceProvider @@ -149,8 +149,6 @@ All service providers are registered in the `bootstrap/providers.php` configurat [!WARNING] > 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. -Since Pulse is currently in beta, you may need to adjust your application's `composer.json` file to allow beta package releases to be installed: - -```json -"minimum-stability": "beta", -"prefer-stable": true -``` - -Then, you may use the Composer package manager to install Pulse into your Laravel project: +You may install Pulse using the Composer package manager: ```sh composer require laravel/pulse @@ -165,7 +158,7 @@ public function boot(): void } ``` -> [!NOTE] +> [!NOTE] > 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/{{version}}/container#binding-a-singleton). @@ -176,6 +169,12 @@ public function boot(): void The `` card displays system resource usage for all servers running the `pulse:check` command. Please refer to the documentation regarding the [servers recorder](#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`: + +```blade + +``` + #### Application Usage @@ -191,7 +190,7 @@ If you wish to view all usage metrics on screen at the same time, you may includ To learn how to customize how Pulse retrieves and displays user information, consult our documentation on [resolving users](#dashboard-resolving-users). -> [!NOTE] +> [!NOTE] > If your application receives a lot of requests or dispatches a lot of jobs, you may wish to enable [sampling](#sampling). See the [user requests recorder](#user-requests-recorder), [user jobs recorder](#user-jobs-recorder), and [slow jobs recorder](#slow-jobs-recorder) documentation for more information. @@ -221,6 +220,12 @@ The `` card shows the database queries in your ap 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: + +```blade + +``` + See the [slow queries recorder](#slow-queries-recorder) documentation for more information. @@ -246,7 +251,7 @@ Most Pulse recorders will automatically capture entries based on framework event php artisan pulse:check ``` -> [!NOTE] +> [!NOTE] > 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: @@ -304,6 +309,20 @@ The `SlowJobs` recorder captures information about slow jobs occurring in your a You may optionally adjust the slow job threshold, [sample rate](#sampling), 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: + +```php +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 @@ -311,10 +330,24 @@ The `SlowOutgoingRequests` recorder captures information about outgoing HTTP req You may optionally adjust the slow outgoing request threshold, [sample rate](#sampling), 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: + +```php +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: ```php -Recorders\OutgoingRequests::class => [ +Recorders\SlowOutgoingRequests::class => [ // ... 'groups' => [ // '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*', @@ -333,6 +366,20 @@ The `SlowQueries` recorder captures any database queries in your application tha You may optionally adjust the slow query threshold, [sample rate](#sampling), 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: + +```php +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 @@ -340,6 +387,20 @@ The `Requests` recorder captures information about requests made to your applica You may optionally adjust the slow route threshold, [sample rate](#sampling), 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: + +```php +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 @@ -365,7 +426,7 @@ You may optionally adjust the [sample rate](#sampling) and ignored job patterns. The `UserRequests` recorder captures information about the users making requests to your application for display on the [Application Usage](#application-usage-card) card. -You may optionally adjust the [sample rate](#sampling) and ignored job patterns. +You may optionally adjust the [sample rate](#sampling) and ignored URL patterns. ### Filtering @@ -410,7 +471,7 @@ PULSE_DB_CONNECTION=pulse ### Redis Ingest -> [!WARNING] +> [!WARNING] > 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](#using-a-different-database) 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: @@ -431,7 +492,7 @@ When using the Redis ingest, you will need to run the `pulse:work` command to mo php artisan pulse:work ``` -> [!NOTE] +> [!NOTE] > 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: @@ -532,7 +593,7 @@ Once you have defined your Livewire component and template, the card may be incl ``` -> [!NOTE] +> [!NOTE] > If your card is included in a package, you will need to register the component with Livewire using the `Livewire::component` method. @@ -646,7 +707,7 @@ The available aggregation methods are: * `min` * `sum` -> [!NOTE] +> [!NOTE] > When building a card package that captures the currently authenticated user ID, you should use the `Pulse::resolveAuthenticatedUserId()` method, which respects any [user resolver customizations](#dashboard-resolving-users) made to the application. @@ -660,13 +721,13 @@ class TopSellers extends Card public function render() { return view('livewire.pulse.top-sellers', [ - 'topSellers' => $this->aggregate('user_sale', ['sum', 'count']); + 'topSellers' => $this->aggregate('user_sale', ['sum', 'count']) ]); } } ``` -The `aggregate` method returns return a collection of PHP `stdClass` objects. Each object will contain the `key` property captured earlier, along with keys for each of the requested aggregates: +The `aggregate` method returns a collection of PHP `stdClass` objects. Each object will contain the `key` property captured earlier, along with keys for each of the requested aggregates: ``` @foreach ($topSellers as $seller) diff --git a/queries.md b/queries.md index 94ff47f6905..4704519e22a 100644 --- a/queries.md +++ b/queries.md @@ -13,7 +13,7 @@ - [Where Clauses](#where-clauses) - [Or Where Clauses](#or-where-clauses) - [Where Not Clauses](#where-not-clauses) - - [Where Any / All Clauses](#where-any-all-clauses) + - [Where Any / All / None Clauses](#where-any-all-none-clauses) - [JSON Where Clauses](#json-where-clauses) - [Additional Where Clauses](#additional-where-clauses) - [Logical Grouping](#logical-grouping) @@ -95,6 +95,10 @@ If you just need to retrieve a single row from a database table, you may use the return $user->email; +If you would like to retrieve a single row from a database table, but throw an `Illuminate\Database\RecordNotFoundException` if no matching row is found, you may use the `firstOrFail` method. If the `RecordNotFoundException` is not caught, a 404 HTTP response is automatically sent back to the client: + + $user = DB::table('users')->where('name', 'John')->firstOrFail(); + If you don't need an entire row, you may extract a single value from a record using the `value` method. This method will return the value of the column directly: $email = DB::table('users')->where('name', 'John')->value('email'); @@ -116,7 +120,7 @@ If you would like to retrieve an `Illuminate\Support\Collection` instance contai echo $title; } - You may specify the column that the resulting collection should use as its keys by providing a second argument to the `pluck` method: +You may specify the column that the resulting collection should use as its keys by providing a second argument to the `pluck` method: $titles = DB::table('users')->pluck('title', 'name'); @@ -157,6 +161,20 @@ If you are updating database records while chunking results, your chunk results } }); +Since the `chunkById` and `lazyById` methods add their own "where" conditions to the query being executed, you should typically [logically group](#logical-grouping) your own conditions within a closure: + +```php +DB::table('users')->where(function ($query) { + $query->where('credits', 1)->orWhere('credits', 2); +})->chunkById(100, function (Collection $users) { + foreach ($users as $user) { + DB::table('users') + ->where('id', $user->id) + ->update(['credits' => 3]); + } +}); +``` + > [!WARNING] > When updating or deleting records inside the chunk callback, any changes to the primary key or foreign keys could affect the chunk query. This could potentially result in records not being included in the chunked results. @@ -258,7 +276,7 @@ Sometimes you may need to insert an arbitrary string into a query. To create a r ### Raw Methods -Instead of using the `DB::raw` method, you may also use the following methods to insert a raw expression into various parts of your query. **Remember, Laravel can not guarantee that any query using raw expressions is protected against SQL injection vulnerabilities.** +Instead of using the `DB::raw` method, you may also use the following methods to insert a raw expression into various parts of your query. **Remember, Laravel cannot guarantee that any query using raw expressions is protected against SQL injection vulnerabilities.** #### `selectRaw` @@ -461,6 +479,9 @@ You may also pass an array of conditions to the `where` function. Each element o > [!WARNING] > PDO does not support binding column names. Therefore, you should never allow user input to dictate the column names referenced by your queries, including "order by" columns. +> [!WARNING] +> MySQL and MariaDB automatically typecast strings to integers in string-number comparisons. In this process, non-numeric strings are converted to `0`, which can lead to unexpected results. For example, if your table has a `secret` column with a value of `aaa` and you run `User::where('secret', 0)`, that row will be returned. To avoid this, ensure all values are typecast to their appropriate types before using them in queries. + ### Or Where Clauses @@ -502,8 +523,8 @@ The `whereNot` and `orWhereNot` methods may be used to negate a given group of q }) ->get(); - -### Where Any / All Clauses + +### Where Any / All / None Clauses Sometimes you may need to apply the same query constraints to multiple columns. For example, you may want to retrieve all records where any columns in a given list are `LIKE` a given value. You may accomplish this using the `whereAny` method: @@ -513,7 +534,7 @@ Sometimes you may need to apply the same query constraints to multiple columns. 'name', 'email', 'phone', - ], 'LIKE', 'Example%') + ], 'like', 'Example%') ->get(); The query above will result in the following SQL: @@ -535,7 +556,7 @@ Similarly, the `whereAll` method may be used to retrieve records where all of th ->whereAll([ 'title', 'content', - ], 'LIKE', '%Laravel%') + ], 'like', '%Laravel%') ->get(); The query above will result in the following SQL: @@ -549,10 +570,33 @@ WHERE published = true AND ( ) ``` +The `whereNone` method may be used to retrieve records where none of the given columns match a given constraint: + + $posts = DB::table('albums') + ->where('published', true) + ->whereNone([ + 'title', + 'lyrics', + 'tags', + ], 'like', '%explicit%') + ->get(); + +The query above will result in the following SQL: + +```sql +SELECT * +FROM albums +WHERE published = true AND NOT ( + title LIKE '%explicit%' OR + lyrics LIKE '%explicit%' OR + tags LIKE '%explicit%' +) +``` + ### JSON Where Clauses -Laravel also supports querying JSON column types on databases that provide support for JSON column types. Currently, this includes MySQL 8.0+, PostgreSQL 12.0+, SQL Server 2017+, and SQLite 3.39.0+ (with the [JSON1 extension](https://www.sqlite.org/json1.html)). To query a JSON column, use the `->` operator: +Laravel also supports querying JSON column types on databases that provide support for JSON column types. Currently, this includes MariaDB 10.3+, MySQL 8.0+, PostgreSQL 12.0+, SQL Server 2017+, and SQLite 3.39.0+. To query a JSON column, use the `->` operator: $users = DB::table('users') ->where('preferences->dining->meal', 'salad') @@ -564,7 +608,7 @@ You may use `whereJsonContains` to query JSON arrays: ->whereJsonContains('options->languages', 'en') ->get(); -If your application uses the MySQL or PostgreSQL databases, you may pass an array of values to the `whereJsonContains` method: +If your application uses the MariaDB, MySQL, or PostgreSQL databases, you may pass an array of values to the `whereJsonContains` method: $users = DB::table('users') ->whereJsonContains('options->languages', ['en', 'de']) @@ -583,35 +627,42 @@ You may use `whereJsonLength` method to query JSON arrays by their length: ### Additional Where Clauses -**whereBetween / orWhereBetween** +**whereLike / orWhereLike / whereNotLike / orWhereNotLike** -The `whereBetween` method verifies that a column's value is between two values: +The `whereLike` method allows you to add "LIKE" clauses to your query for pattern matching. These methods provide a database-agnostic way of performing string matching queries, with the ability to toggle case-sensitivity. By default, string matching is case-insensitive: $users = DB::table('users') - ->whereBetween('votes', [1, 100]) + ->whereLike('name', '%John%') ->get(); -**whereNotBetween / orWhereNotBetween** +You can enable a case-sensitive search via the `caseSensitive` argument: -The `whereNotBetween` method verifies that a column's value lies outside of two values: + $users = DB::table('users') + ->whereLike('name', '%John%', caseSensitive: true) + ->get(); + +The `orWhereLike` method allows you to add an "or" clause with a LIKE condition: $users = DB::table('users') - ->whereNotBetween('votes', [1, 100]) - ->get(); + ->where('votes', '>', 100) + ->orWhereLike('name', '%John%') + ->get(); -**whereBetweenColumns / whereNotBetweenColumns / orWhereBetweenColumns / orWhereNotBetweenColumns** +The `whereNotLike` method allows you to add "NOT LIKE" clauses to your query: -The `whereBetweenColumns` method verifies that a column's value is between the two values of two columns in the same table row: + $users = DB::table('users') + ->whereNotLike('name', '%John%') + ->get(); - $patients = DB::table('patients') - ->whereBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight']) - ->get(); +Similarly, you can use `orWhereNotLike` to add an "or" clause with a NOT LIKE condition: -The `whereNotBetweenColumns` method verifies that a column's value lies outside the two values of two columns in the same table row: + $users = DB::table('users') + ->where('votes', '>', 100) + ->orWhereNotLike('name', '%John%') + ->get(); - $patients = DB::table('patients') - ->whereNotBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight']) - ->get(); +> [!WARNING] +> The `whereLike` case-sensitive search option is currently not supported on SQL Server. **whereIn / whereNotIn / orWhereIn / orWhereNotIn** @@ -648,6 +699,36 @@ select * from comments where user_id in ( > [!WARNING] > If you are adding a large array of integer bindings to your query, the `whereIntegerInRaw` or `whereIntegerNotInRaw` methods may be used to greatly reduce your memory usage. +**whereBetween / orWhereBetween** + +The `whereBetween` method verifies that a column's value is between two values: + + $users = DB::table('users') + ->whereBetween('votes', [1, 100]) + ->get(); + +**whereNotBetween / orWhereNotBetween** + +The `whereNotBetween` method verifies that a column's value lies outside of two values: + + $users = DB::table('users') + ->whereNotBetween('votes', [1, 100]) + ->get(); + +**whereBetweenColumns / whereNotBetweenColumns / orWhereBetweenColumns / orWhereNotBetweenColumns** + +The `whereBetweenColumns` method verifies that a column's value is between the two values of two columns in the same table row: + + $patients = DB::table('patients') + ->whereBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight']) + ->get(); + +The `whereNotBetweenColumns` method verifies that a column's value lies outside the two values of two columns in the same table row: + + $patients = DB::table('patients') + ->whereNotBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight']) + ->get(); + **whereNull / whereNotNull / orWhereNull / orWhereNotNull** The `whereNull` method verifies that the value of the given column is `NULL`: @@ -739,7 +820,7 @@ select * from users where name = 'John' and (votes > 100 or title = 'Admin') > You should always group `orWhere` calls in order to avoid unexpected behavior when global scopes are applied. -### Advanced Where Clauses +## Advanced Where Clauses ### Where Exists Clauses @@ -804,9 +885,9 @@ Or, you may need to construct a "where" clause that compares a column to the res ### Full Text Where Clauses > [!WARNING] -> Full text where clauses are currently supported by MySQL and PostgreSQL. +> Full text where clauses are currently supported by MariaDB, MySQL, and PostgreSQL. -The `whereFullText` and `orWhereFullText` methods may be used to add full text "where" clauses to a query for columns that have [full text indexes](/docs/{{version}}/migrations#available-index-types). These methods will be transformed into the appropriate SQL for the underlying database system by Laravel. For example, a `MATCH AGAINST` clause will be generated for applications utilizing MySQL: +The `whereFullText` and `orWhereFullText` methods may be used to add full text "where" clauses to a query for columns that have [full text indexes](/docs/{{version}}/migrations#available-index-types). These methods will be transformed into the appropriate SQL for the underlying database system by Laravel. For example, a `MATCH AGAINST` clause will be generated for applications utilizing MariaDB or MySQL: $users = DB::table('users') ->whereFullText('bio', 'web developer') @@ -919,7 +1000,7 @@ Alternatively, you may use the `limit` and `offset` methods. These methods are f Sometimes you may want certain query clauses to apply to a query based on another condition. For instance, you may only want to apply a `where` statement if a given input value is present on the incoming HTTP request. You may accomplish this using the `when` method: - $role = $request->string('role'); + $role = $request->input('role'); $users = DB::table('users') ->when($role, function (Builder $query, string $role) { @@ -1002,7 +1083,7 @@ The `upsert` method will insert records that do not exist and update the records In the example above, Laravel will attempt to insert two records. If a record already exists with the same `departure` and `destination` column values, Laravel will update that record's `price` column. > [!WARNING] -> All databases except SQL Server require the columns in the second argument of the `upsert` method to have a "primary" or "unique" index. In addition, the MySQL database driver ignores the second argument of the `upsert` method and always uses the "primary" and "unique" indexes of the table to detect existing records. +> 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. ## Update Statements @@ -1018,7 +1099,7 @@ In addition to inserting records into the database, the query builder can also u Sometimes you may want to update an existing record in the database or create it if no matching record exists. In this scenario, the `updateOrInsert` method may be used. The `updateOrInsert` method accepts two arguments: an array of conditions by which to find the record, and an array of column and value pairs indicating the columns to be updated. -The `updateOrInsert` method will attempt to locate a matching database record using the first argument's column and value pairs. If the record exists, it will be updated with the values in the second argument. If the record can not be found, a new record will be inserted with the merged attributes of both arguments: +The `updateOrInsert` method will attempt to locate a matching database record using the first argument's column and value pairs. If the record exists, it will be updated with the values in the second argument. If the record cannot be found, a new record will be inserted with the merged attributes of both arguments: DB::table('users') ->updateOrInsert( @@ -1026,10 +1107,26 @@ The `updateOrInsert` method will attempt to locate a matching database record us ['votes' => '2'] ); +You may provide a closure to the `updateOrInsert` method to customize the attributes that are updated or inserted into the database based on the existence of a matching record: + +```php +DB::table('users')->updateOrInsert( + ['user_id' => $user_id], + fn ($exists) => $exists ? [ + 'name' => $data['name'], + 'email' => $data['email'], + ] : [ + 'name' => $data['name'], + 'email' => $data['email'], + 'marketable' => true, + ], +); +``` + ### Updating JSON Columns -When updating a JSON column, you should use `->` syntax to update the appropriate key in the JSON object. This operation is supported on MySQL 5.7+ and PostgreSQL 9.5+: +When updating a JSON column, you should use `->` syntax to update the appropriate key in the JSON object. This operation is supported on MariaDB 10.3+, MySQL 5.7+, and PostgreSQL 9.5+: $affected = DB::table('users') ->where('id', 1) diff --git a/queues.md b/queues.md index 57c3f6fecde..f90404984b2 100644 --- a/queues.md +++ b/queues.md @@ -12,6 +12,7 @@ - [Rate Limiting](#rate-limiting) - [Preventing Job Overlaps](#preventing-job-overlaps) - [Throttling Exceptions](#throttling-exceptions) + - [Skipping Jobs](#skipping-jobs) - [Dispatching Jobs](#dispatching-jobs) - [Delayed Dispatching](#delayed-dispatching) - [Synchronous Dispatching](#synchronous-dispatching) @@ -150,6 +151,7 @@ The following dependencies are needed for the listed queue drivers. These depend - Amazon SQS: `aws/aws-sdk-php ~3.0` - Beanstalkd: `pda/pheanstalk ~5.0` - Redis: `predis/predis ~2.0` or phpredis PHP extension +- [MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/queues/): `mongodb/laravel-mongodb` @@ -181,15 +183,12 @@ Job classes are very simple, normally containing only a `handle` method that is use App\Models\Podcast; use App\Services\AudioProcessor; - use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; - use Illuminate\Foundation\Bus\Dispatchable; - use Illuminate\Queue\InteractsWithQueue; - use Illuminate\Queue\SerializesModels; + use Illuminate\Foundation\Queue\Queueable; class ProcessPodcast implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Queueable; /** * Create a new job instance. @@ -207,7 +206,7 @@ Job classes are very simple, normally containing only a `handle` method that is } } -In this example, note that we were able to pass an [Eloquent model](/docs/{{version}}/eloquent) directly into the queued job's constructor. Because of the `SerializesModels` trait that the job is using, Eloquent models and their loaded relationships will be gracefully serialized and unserialized when the job is processing. +In this example, note that we were able to pass an [Eloquent model](/docs/{{version}}/eloquent) directly into the queued job's constructor. Because of the `Queueable` trait that the job is using, Eloquent models and their loaded relationships will be gracefully serialized and unserialized when the job is processing. If your queued job accepts an Eloquent model in its constructor, only the identifier for the model will be serialized onto the queue. When the job is actually handled, the queue system will automatically re-retrieve the full model instance and its loaded relationships from the database. This approach to model serialization allows for much smaller job payloads to be sent to your queue driver. @@ -239,8 +238,9 @@ Or, to prevent relations from being serialized, you can call the `withoutRelatio /** * Create a new job instance. */ - public function __construct(Podcast $podcast) - { + public function __construct( + Podcast $podcast, + ) { $this->podcast = $podcast->withoutRelations(); } @@ -253,9 +253,8 @@ If you are using PHP constructor property promotion and would like to indicate t */ public function __construct( #[WithoutRelations] - public Podcast $podcast - ) { - } + public Podcast $podcast, + ) {} If a job receives a collection or array of Eloquent models instead of a single model, the models within that collection will not have their relationships restored when the job is deserialized and executed. This is to prevent excessive resource usage on jobs that deal with large numbers of models. @@ -575,7 +574,6 @@ class ProviderIsDown { // ... - public function middleware(): array { return [ @@ -588,7 +586,6 @@ class ProviderIsUp { // ... - public function middleware(): array { return [ @@ -615,7 +612,7 @@ For example, let's imagine a queued job that interacts with a third-party API th */ public function middleware(): array { - return [new ThrottlesExceptions(10, 5)]; + return [new ThrottlesExceptions(10, 5 * 60)]; } /** @@ -623,10 +620,10 @@ For example, let's imagine a queued job that interacts with a third-party API th */ public function retryUntil(): DateTime { - return now()->addMinutes(5); + return now()->addMinutes(30); } -The first constructor argument accepted by the middleware is the number of exceptions the job can throw before being throttled, while the second constructor argument is the number of minutes that should elapse before the job is attempted again once it has been throttled. In the code example above, if the job throws 10 exceptions within 5 minutes, we will wait 5 minutes before attempting the job again. +The first constructor argument accepted by the middleware is the number of exceptions the job can throw before being throttled, while the second constructor argument is the number of seconds that should elapse before the job is attempted again once it has been throttled. In the code example above, if the job throws 10 consecutive exceptions, we will wait 5 minutes before attempting the job again, constrained by the 30-minute time limit. When a job throws an exception but the exception threshold has not yet been reached, the job will typically be retried immediately. However, you may specify the number of minutes such a job should be delayed by calling the `backoff` method when attaching the middleware to the job: @@ -639,7 +636,7 @@ When a job throws an exception but the exception threshold has not yet been reac */ public function middleware(): array { - return [(new ThrottlesExceptions(10, 5))->backoff(5)]; + return [(new ThrottlesExceptions(10, 5 * 60))->backoff(5)]; } Internally, this middleware uses Laravel's cache system to implement rate limiting, and the job's class name is utilized as the cache "key". You may override this key by calling the `by` method when attaching the middleware to your job. This may be useful if you have multiple jobs interacting with the same third-party service and you would like them to share a common throttling "bucket": @@ -653,7 +650,7 @@ Internally, this middleware uses Laravel's cache system to implement rate limiti */ public function middleware(): array { - return [(new ThrottlesExceptions(10, 10))->by('key')]; + return [(new ThrottlesExceptions(10, 10 * 60))->by('key')]; } By default, this middleware will throttle every exception. You can modify this behaviour by invoking the `when` method when attaching the middleware to your job. The exception will then only be throttled if closure provided to the `when` method returns `true`: @@ -668,7 +665,7 @@ By default, this middleware will throttle every exception. You can modify this b */ public function middleware(): array { - return [(new ThrottlesExceptions(10, 10))->when( + return [(new ThrottlesExceptions(10, 10 * 60))->when( fn (Throwable $throwable) => $throwable instanceof HttpClientException )]; } @@ -685,7 +682,7 @@ If you would like to have the throttled exceptions reported to your application' */ public function middleware(): array { - return [(new ThrottlesExceptions(10, 10))->report( + return [(new ThrottlesExceptions(10, 10 * 60))->report( fn (Throwable $throwable) => $throwable instanceof HttpClientException )]; } @@ -693,6 +690,39 @@ If you would like to have the throttled exceptions reported to your application' > [!NOTE] > If you are using Redis, you may use the `Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis` middleware, which is fine-tuned for Redis and more efficient than the basic exception throttling middleware. + +### Skipping Jobs + +The `Skip` middleware allows you to specify that a job should be skipped / deleted without needing to modify the job's logic. The `Skip::when` method will delete the job if the given condition evaluates to `true`, while the `Skip::unless` method will delete the job if the condition evaluates to `false`: + + use Illuminate\Queue\Middleware\Skip; + + /** + * Get the middleware the job should pass through. + */ + public function middleware(): array + { + return [ + Skip::when($someCondition), + ]; + } + +You can also pass a `Closure` to the `when` and `unless` methods for more complex conditional evaluation: + + use Illuminate\Queue\Middleware\Skip; + + /** + * Get the middleware the job should pass through. + */ + public function middleware(): array + { + return [ + Skip::when(function (): bool { + return $this->shouldSkip(); + }), + ]; + } + ## Dispatching Jobs @@ -766,6 +796,10 @@ If you would like to specify that a job should not be immediately available for } } +In some cases, jobs may have a default delay configured. If you need to bypass this delay and dispatch a job for immediate processing, you may use the `withoutDelay` method: + + ProcessPodcast::dispatch($podcast)->withoutDelay(); + > [!WARNING] > The Amazon SQS queue service has a maximum delay time of 15 minutes. @@ -933,7 +967,7 @@ When chaining jobs, you may use the `catch` method to specify a closure that sho > Since chain callbacks are serialized and executed at a later time by the Laravel queue, you should not use the `$this` variable within chain callbacks. -### Customizing The Queue a Connection +### Customizing the Queue and Connection #### Dispatching to a Particular Queue @@ -973,15 +1007,12 @@ Alternatively, you may specify the job's queue by calling the `onQueue` method w namespace App\Jobs; - use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; - use Illuminate\Foundation\Bus\Dispatchable; - use Illuminate\Queue\InteractsWithQueue; - use Illuminate\Queue\SerializesModels; + use Illuminate\Foundation\Queue\Queueable; class ProcessPodcast implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Queueable; /** * Create a new job instance. @@ -1036,15 +1067,12 @@ Alternatively, you may specify the job's connection by calling the `onConnection namespace App\Jobs; - use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; - use Illuminate\Foundation\Bus\Dispatchable; - use Illuminate\Queue\InteractsWithQueue; - use Illuminate\Queue\SerializesModels; + use Illuminate\Foundation\Queue\Queueable; class ProcessPodcast implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Queueable; /** * Create a new job instance. @@ -1277,15 +1305,12 @@ To define a batchable job, you should [create a queueable job](#creating-jobs) a namespace App\Jobs; use Illuminate\Bus\Batchable; - use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; - use Illuminate\Foundation\Bus\Dispatchable; - use Illuminate\Queue\InteractsWithQueue; - use Illuminate\Queue\SerializesModels; + use Illuminate\Foundation\Queue\Queueable; class ImportCsv implements ShouldQueue { - use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Batchable, Queueable; /** * Execute the job. @@ -1595,7 +1620,7 @@ Then, set the `queue.batching.driver` configuration option's value to `dynamodb` ```php 'batching' => [ - 'driver' => env('QUEUE_FAILED_DRIVER', 'dynamodb'), + 'driver' => env('QUEUE_BATCHING_DRIVER', 'dynamodb'), 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), @@ -1625,7 +1650,7 @@ If you defined your DynamoDB table with a `ttl` attribute, you may define config ## Queueing Closures -Instead of dispatching a job class to the queue, you may also dispatch a closure. This is great for quick, simple tasks that need to be executed outside of the current request cycle. When dispatching closures to the queue, the closure's code content is cryptographically signed so that it can not be modified in transit: +Instead of dispatching a job class to the queue, you may also dispatch a closure. This is great for quick, simple tasks that need to be executed outside of the current request cycle. When dispatching closures to the queue, the closure's code content is cryptographically signed so that it cannot be modified in transit: $podcast = App\Podcast::find(1); @@ -1932,15 +1957,13 @@ When a particular job fails, you may want to send an alert to your users or reve use App\Models\Podcast; use App\Services\AudioProcessor; - use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; - use Illuminate\Queue\InteractsWithQueue; - use Illuminate\Queue\SerializesModels; + use Illuminate\Foundation\Queue\Queueable; use Throwable; class ProcessPodcast implements ShouldQueue { - use InteractsWithQueue, Queueable, SerializesModels; + use Queueable; /** * Create a new job instance. @@ -2411,7 +2434,7 @@ In addition, you may occasionally need to test an individual job's interaction w Sometimes, you may need to test that a queued job [releases itself back onto the queue](#manually-releasing-a-job). Or, you may need to test that the job deleted itself. You may test these queue interactions by instantiating the job and invoking the `withFakeQueueInteractions` method. -Once the job's queue interactions have been faked, you may invoke the `handle` method on the job. After invoking the job, the `assetReleased`, `assertDeleted`, and `assertFailed` methods may be used to make assertions against the job's queue interactions: +Once the job's queue interactions have been faked, you may invoke the `handle` method on the job. After invoking the job, the `assertReleased`, `assertDeleted`, `assertNotDeleted`, `assertFailed`, and `assertNotFailed` methods may be used to make assertions against the job's queue interactions: ```php use App\Jobs\ProcessPodcast; @@ -2422,7 +2445,9 @@ $job->handle(); $job->assertReleased(delay: 30); $job->assertDeleted(); +$job->assertNotDeleted(); $job->assertFailed(); +$job->assertNotFailed(); ``` diff --git a/rate-limiting.md b/rate-limiting.md index a1bd0c3b87f..8492dda2231 100644 --- a/rate-limiting.md +++ b/rate-limiting.md @@ -12,7 +12,7 @@ Laravel includes a simple to use rate limiting abstraction which, in conjunction with your application's [cache](cache), provides an easy way to limit any action during a specified window of time. > [!NOTE] -> If you are interested in rate limiting incoming HTTP requests, please consult the [rate limiter middleware documentation](routing#rate-limiting). +> If you are interested in rate limiting incoming HTTP requests, please consult the [rate limiter middleware documentation](/docs/{{version}}/routing#rate-limiting). ### Cache Configuration diff --git a/releases.md b/releases.md index 6a3663c05ed..ff5401eae82 100644 --- a/releases.md +++ b/releases.md @@ -21,15 +21,14 @@ When referencing the Laravel framework or its components from your application o For all Laravel releases, bug fixes are provided for 18 months and security fixes are provided for 2 years. For all additional libraries, including Lumen, only the latest major release receives bug fixes. In addition, please review the database versions [supported by Laravel](/docs/{{version}}/database#introduction). -
| Version | PHP (*) | Release | Bug Fixes Until | Security Fixes Until | | --- | --- | --- | --- | --- | | 9 | 8.0 - 8.2 | February 8th, 2022 | August 8th, 2023 | February 6th, 2024 | | 10 | 8.1 - 8.3 | February 14th, 2023 | August 6th, 2024 | February 4th, 2025 | -| 11 | 8.2 - 8.3 | March 12th, 2024 | September 3rd, 2025 | March 12th, 2026 | -| 12 | 8.2 - 8.3 | Q1 2025 | Q3, 2026 | Q1, 2027 | +| 11 | 8.2 - 8.4 | March 12th, 2024 | September 3rd, 2025 | March 12th, 2026 | +| 12 | 8.2 - 8.4 | Q1 2025 | Q3 2026 | Q1 2027 |
diff --git a/requests.md b/requests.md index e2dbc005f27..4b4cc9e412d 100644 --- a/requests.md +++ b/requests.md @@ -306,10 +306,17 @@ When sending JSON requests to your application, you may access the JSON data via #### Retrieving Stringable Input Values -Instead of retrieving the request's input data as a primitive `string`, you may use the `string` method to retrieve the request data as an instance of [`Illuminate\Support\Stringable`](/docs/{{version}}/helpers#fluent-strings): +Instead of retrieving the request's input data as a primitive `string`, you may use the `string` method to retrieve the request data as an instance of [`Illuminate\Support\Stringable`](/docs/{{version}}/strings): $name = $request->string('name')->trim(); + +#### Retrieving Integer Input Values + +To retrieve input values as integers, you may use the `integer` method. This method will attempt to cast the input value to an integer. If the input is not present or the cast fails, it will return the default value you specify. This is particularly useful for pagination or other numeric inputs: + + $perPage = $request->integer('per_page'); + #### Retrieving Boolean Input Values @@ -339,6 +346,12 @@ Input values that correspond to [PHP enums](https://www.php.net/manual/en/langua $status = $request->enum('status', Status::class); +If the input value is an array of values that correspond to a PHP enum, you may use the `enums` method to retrieve the array of values as enum instances: + + use App\Enums\Product; + + $products = $request->enums('products', Product::class); + #### Retrieving Input via Dynamic Properties @@ -405,6 +418,18 @@ If you would like to determine if a value is present on the request and is not a // ... } +If you would like to determine if a value is missing from the request or is an empty string, you may use the `isNotFilled` method: + + if ($request->isNotFilled('name')) { + // ... + } + +When given an array, the `isNotFilled` method will determine if all of the specified values are missing or empty: + + if ($request->isNotFilled(['name', 'email'])) { + // ... + } + The `anyFilled` method returns `true` if any of the specified values is not an empty string: if ($request->anyFilled(['name', 'email'])) { @@ -431,7 +456,7 @@ To determine if a given key is absent from the request, you may use the `missing // ... } - $request->whenMissing('name', function (array $input) { + $request->whenMissing('name', function () { // The "name" value is missing... }, function () { // The "name" value is present... @@ -471,11 +496,11 @@ You may also use the `flashOnly` and `flashExcept` methods to flash a subset of Since you often will want to flash input to the session and then redirect to the previous page, you may easily chain input flashing onto a redirect using the `withInput` method: - return redirect('form')->withInput(); + return redirect('/form')->withInput(); return redirect()->route('user.create')->withInput(); - return redirect('form')->withInput( + return redirect('/form')->withInput( $request->except('password') ); @@ -604,7 +629,7 @@ To solve this, you may enable the `Illuminate\Http\Middleware\TrustProxies` midd ->withMiddleware(function (Middleware $middleware) { $middleware->trustProxies(at: [ '192.168.1.1', - '192.168.1.2', + '10.0.0.0/8', ]); }) @@ -620,7 +645,7 @@ In addition to configuring the trusted proxies, you may also configure the proxy }) > [!NOTE] -> If you are using AWS Elastic Load Balancing, your `headers` value should be `Request::HEADER_X_FORWARDED_AWS_ELB`. For more information on the constants that may be used in the `headers` value, check out Symfony's documentation on [trusting proxies](https://symfony.com/doc/7.0/deployment/proxies.html). +> If you are using AWS Elastic Load Balancing, the `headers` value should be `Request::HEADER_X_FORWARDED_AWS_ELB`. If your load balancer uses the standard `Forwarded` header from [RFC 7239](https://www.rfc-editor.org/rfc/rfc7239#section-4), the `headers` value should be `Request::HEADER_FORWARDED`. For more information on the constants that may be used in the `headers` value, check out Symfony's documentation on [trusting proxies](https://symfony.com/doc/7.0/deployment/proxies.html). #### Trusting All Proxies @@ -650,3 +675,8 @@ By default, requests coming from subdomains of the application's URL are also au $middleware->trustHosts(at: ['laravel.test'], subdomains: false); }) +If you need to access your application's configuration files or database to determine your trusted hosts, you may provide a closure to the `at` argument: + + ->withMiddleware(function (Middleware $middleware) { + $middleware->trustHosts(at: fn () => config('app.trusted_hosts')); + }) diff --git a/responses.md b/responses.md index 30435afc62e..559ed9f3fab 100644 --- a/responses.md +++ b/responses.md @@ -14,6 +14,7 @@ - [JSON Responses](#json-responses) - [File Downloads](#file-downloads) - [File Responses](#file-responses) + - [Streamed Responses](#streamed-responses) - [Response Macros](#response-macros) @@ -152,7 +153,7 @@ By default, thanks to the `Illuminate\Cookie\Middleware\EncryptCookies` middlewa Redirect responses are instances of the `Illuminate\Http\RedirectResponse` class, and contain the proper headers needed to redirect the user to another URL. There are several ways to generate a `RedirectResponse` instance. The simplest method is to use the global `redirect` helper: Route::get('/dashboard', function () { - return redirect('home/dashboard'); + return redirect('/home/dashboard'); }); Sometimes you may wish to redirect the user to their previous location, such as when a submitted form is invalid. You may do so by using the global `back` helper function. Since this feature utilizes the [session](/docs/{{version}}/session), make sure the route calling the `back` function is using the `web` middleware group: @@ -225,7 +226,7 @@ Redirecting to a new URL and [flashing data to the session](/docs/{{version}}/se Route::post('/user/profile', function () { // ... - return redirect('dashboard')->with('status', 'Profile updated!'); + return redirect('/dashboard')->with('status', 'Profile updated!'); }); After the user is redirected, you may display the flashed message from the [session](/docs/{{version}}/session). For example, using [Blade syntax](/docs/{{version}}/blade): @@ -287,6 +288,52 @@ The `download` method may be used to generate a response that forces the user's > [!WARNING] > Symfony HttpFoundation, which manages file downloads, requires the file being downloaded to have an ASCII filename. + +### File Responses + +The `file` method may be used to display a file, such as an image or PDF, directly in the user's browser instead of initiating a download. This method accepts the absolute path to the file as its first argument and an array of headers as its second argument: + + return response()->file($pathToFile); + + return response()->file($pathToFile, $headers); + + +### Streamed Responses + +By streaming data to the client as it is generated, you can significantly reduce memory usage and improve performance, especially for very large responses. Streamed responses allow the client to begin processing data before the server has finished sending it: + + function streamedContent(): Generator { + yield 'Hello, '; + yield 'World!'; + } + + Route::get('/stream', function () { + return response()->stream(function (): void { + foreach (streamedContent() as $chunk) { + echo $chunk; + ob_flush(); + flush(); + sleep(2); // Simulate delay between chunks... + } + }, 200, ['X-Accel-Buffering' => 'no']); + }); + +> [!NOTE] +> Internally, Laravel utilizes PHP's output buffering functionality. As you can see in the example above, you should use the `ob_flush` and `flush` functions to push buffered content to the client. + + +#### Streamed JSON Responses + +If you need to stream JSON data incrementally, you may utilize the `streamJson` method. This method is especially useful for large datasets that need to be sent progressively to the browser in a format that can be easily parsed by JavaScript: + + use App\Models\User; + + Route::get('/users.json', function () { + return response()->streamJson([ + 'users' => User::cursor(), + ]); + }); + #### Streamed Downloads @@ -300,15 +347,6 @@ Sometimes you may wish to turn the string response of a given operation into a d ->readme('laravel', 'laravel')['contents']; }, 'laravel-readme.md'); - -### File Responses - -The `file` method may be used to display a file, such as an image or PDF, directly in the user's browser instead of initiating a download. This method accepts the absolute path to the file as its first argument and an array of headers as its second argument: - - return response()->file($pathToFile); - - return response()->file($pathToFile, $headers); - ## Response Macros diff --git a/reverb.md b/reverb.md index 48065d6dba1..e58460859b3 100644 --- a/reverb.md +++ b/reverb.md @@ -190,6 +190,8 @@ Next, add the Pulse cards for each recorder to your [Pulse dashboard](/docs/{{ve ``` +Connection activity is recorded by polling for new updates on a periodic basis. To ensure this information is rendered correctly on the Pulse dashboard, you must run the `pulse:check` daemon on your Reverb server. If you are running Reverb in a [horizontally scaled](#scaling) configuration, you should only run this daemon on one of your servers. + ## Running Reverb in Production @@ -225,13 +227,9 @@ forge hard nofile 10000 Under the hood, Reverb uses a ReactPHP event loop to manage WebSocket connections on the server. By default, this event loop is powered by `stream_select`, which doesn't require any additional extensions. However, `stream_select` is typically limited to 1,024 open files. As such, if you plan to handle more than 1,000 concurrent connections, you will need to use an alternative event loop not bound to the same restrictions. -Reverb will automatically switch to an `ext-event`, `ext-ev`, or `ext-uv` powered loop when available. All of these PHP extensions are available for install via PECL: +Reverb will automatically switch to an `ext-uv` powered loop when available. This PHP extension is available for install via PECL: ```sh -pecl install event -# or -pecl install ev -# or pecl install uv ``` @@ -261,6 +259,9 @@ server { } ``` +> [!WARNING] +> Reverb listens for WebSocket connections at `/app` and handles API requests at `/apps`. You should ensure the web server handling Reverb requests can serve both of these URIs. If you are using [Laravel Forge](https://forge.laravel.com) to manage your servers, your Reverb server will be correctly configured by default. + Typically, web servers are configured to limit the number of allowed connections in order to prevent overloading the server. To increase the number of allowed connections on an Nginx web server to 10,000, the `worker_rlimit_nofile` and `worker_connections` values of the `nginx.conf` file should be updated: ```nginx @@ -284,7 +285,7 @@ The configuration above will allow up to 10,000 Nginx workers per process to be Unix-based operating systems typically limit the number of ports which can be opened on the server. You may see the current allowed range via the following command: ```sh - cat /proc/sys/net/ipv4/ip_local_port_range +cat /proc/sys/net/ipv4/ip_local_port_range # 32768 60999 ``` diff --git a/routing.md b/routing.md index eb8cd83a4b3..3fda01168da 100644 --- a/routing.md +++ b/routing.md @@ -317,13 +317,17 @@ For convenience, some commonly used regular expression patterns have helper meth })->whereUuid('id'); Route::get('/user/{id}', function (string $id) { - // + // ... })->whereUlid('id'); Route::get('/category/{category}', function (string $category) { // ... })->whereIn('category', ['movie', 'song', 'painting']); + Route::get('/category/{category}', function (string $category) { + // ... + })->whereIn('category', CategoryEnum::cases()); + If the incoming request does not match the route pattern constraints, a 404 HTTP response will be returned. @@ -475,7 +479,7 @@ If a group of routes all utilize the same [controller](/docs/{{version}}/control Route groups may also be used to handle subdomain routing. Subdomains may be assigned route parameters just like route URIs, allowing you to capture a portion of the subdomain for usage in your route or controller. The subdomain may be specified by calling the `domain` method before defining the group: Route::domain('{account}.example.com')->group(function () { - Route::get('user/{id}', function (string $account, string $id) { + Route::get('/user/{id}', function (string $account, string $id) { // ... }); }); @@ -548,7 +552,6 @@ Typically, implicit model binding will not retrieve models that have been [soft return $user->email; })->withTrashed(); - #### Customizing the Key @@ -610,7 +613,7 @@ Similarly, you may explicitly instruct Laravel to not scope bindings by invoking #### Customizing Missing Model Behavior -Typically, a 404 HTTP response will be generated if an implicitly bound model is not found. However, you may customize this behavior by calling the `missing` method when defining your route. The `missing` method accepts a closure that will be invoked if an implicitly bound model can not be found: +Typically, a 404 HTTP response will be generated if an implicitly bound model is not found. However, you may customize this behavior by calling the `missing` method when defining your route. The `missing` method accepts a closure that will be invoked if an implicitly bound model cannot be found: use App\Http\Controllers\LocationsController; use Illuminate\Http\Request; @@ -734,9 +737,6 @@ Using the `Route::fallback` method, you may define a route that will be executed // ... }); -> [!WARNING] -> The fallback route should always be the last route registered by your application. - ## Rate Limiting @@ -826,6 +826,15 @@ If needed, you may return an array of rate limits for a given rate limiter confi ]; }); +If you're assigning multiple rate limits segmented by identical `by` values, you should ensure that each `by` value is unique. The easiest way to achieve this is to prefix the values given to the `by` method: + + RateLimiter::for('uploads', function (Request $request) { + return [ + Limit::perMinute(10)->by('minute:'.$request->user()->id), + Limit::perDay(1000)->by('day:'.$request->user()->id), + ]; + }); + ### Attaching Rate Limiters to Routes diff --git a/sail.md b/sail.md index a2a87744b2b..a9932622b25 100644 --- a/sail.md +++ b/sail.md @@ -13,7 +13,9 @@ - [Executing Node / NPM Commands](#executing-node-npm-commands) - [Interacting With Databases](#interacting-with-sail-databases) - [MySQL](#mysql) + - [MongoDB](#mongodb) - [Redis](#redis) + - [Valkey](#valkey) - [Meilisearch](#meilisearch) - [Typesense](#typesense) - [File Storage](#file-storage) @@ -177,7 +179,7 @@ sail php script.php Composer commands may be executed using the `composer` command. Laravel Sail's application container includes a Composer installation: -```nothing +```shell sail composer require laravel/sanctum ``` @@ -193,11 +195,11 @@ docker run --rm \ -u "$(id -u):$(id -g)" \ -v "$(pwd):/var/www/html" \ -w /var/www/html \ - laravelsail/php83-composer:latest \ + laravelsail/php84-composer:latest \ composer install --ignore-platform-reqs ``` -When using the `laravelsail/phpXX-composer` image, you should use the same version of PHP that you plan to use for your application (`80`, `81`, `82`, or `83`). +When using the `laravelsail/phpXX-composer` image, you should use the same version of PHP that you plan to use for your application (`80`, `81`, `82`, `83`, or `84`). ### Executing Artisan Commands @@ -239,24 +241,48 @@ Once you have started your containers, you may connect to the MySQL instance wit To connect to your application's MySQL database from your local machine, you may use a graphical database management application such as [TablePlus](https://tableplus.com). By default, the MySQL database is accessible at `localhost` port 3306 and the access credentials correspond to the values of your `DB_USERNAME` and `DB_PASSWORD` environment variables. Or, you may connect as the `root` user, which also utilizes the value of your `DB_PASSWORD` environment variable as its password. + +### MongoDB + +If you chose to install the [MongoDB](https://www.mongodb.com/) service when installing Sail, your application's `docker-compose.yml` file contains an entry for a [MongoDB Atlas Local](https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-local-cloud/) container which provides the MongoDB document database with Atlas features like [Search Indexes](https://www.mongodb.com/docs/atlas/atlas-search/). This container uses a [Docker volume](https://docs.docker.com/storage/volumes/) so that the data stored in your database is persisted even when stopping and restarting your containers. + +Once you have started your containers, you may connect to the MongoDB instance within your application by setting your `MONGODB_URI` environment variable within your application's `.env` file to `mongodb://mongodb:27017`. Authentication is disabled by default, but you can set the `MONGODB_USERNAME` and `MONGODB_PASSWORD` environment variables to enable authentication before starting the `mongodb` container. Then, add the credentials to the connection string: + +```ini +MONGODB_USERNAME=user +MONGODB_PASSWORD=laravel +MONGODB_URI=mongodb://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@mongodb:27017 +``` + +For seamless integration of MongoDB with your application, you can install the [official package maintained by MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/). + +To connect to your application's MongoDB database from your local machine, you may use a graphical interface such as [Compass](https://www.mongodb.com/products/tools/compass). By default, the MongoDB database is accessible at `localhost` port `27017`. + ### Redis -Your application's `docker-compose.yml` file also contains an entry for a [Redis](https://redis.io) container. This container uses a [Docker volume](https://docs.docker.com/storage/volumes/) so that the data stored in your Redis data is persisted even when stopping and restarting your containers. Once you have started your containers, you may connect to the Redis instance within your application by setting your `REDIS_HOST` environment variable within your application's `.env` file to `redis`. +Your application's `docker-compose.yml` file also contains an entry for a [Redis](https://redis.io) container. This container uses a [Docker volume](https://docs.docker.com/storage/volumes/) so that the data stored in your Redis instance is persisted even when stopping and restarting your containers. Once you have started your containers, you may connect to the Redis instance within your application by setting your `REDIS_HOST` environment variable within your application's `.env` file to `redis`. To connect to your application's Redis database from your local machine, you may use a graphical database management application such as [TablePlus](https://tableplus.com). By default, the Redis database is accessible at `localhost` port 6379. + +### Valkey + +If you choose to install Valkey service when installing Sail, your application's `docker-compose.yml` file will contain an entry for [Valkey](https://valkey.io/). This container uses a [Docker volume](https://docs.docker.com/storage/volumes/) so that the data stored in your Valkey instance is persisted even when stopping and restarting your containers. You can connect to this container in you application by setting your `REDIS_HOST` environment variable within your application's `.env` file to `valkey`. + +To connect to your application's Valkey database from your local machine, you may use a graphical database management application such as [TablePlus](https://tableplus.com). By default, the Valkey database is accessible at `localhost` port 6379. + ### Meilisearch -If you chose to install the [Meilisearch](https://www.meilisearch.com) service when installing Sail, your application's `docker-compose.yml` file will contain an entry for this powerful search-engine that is [compatible](https://github.com/meilisearch/meilisearch-laravel-scout) with [Laravel Scout](/docs/{{version}}/scout). Once you have started your containers, you may connect to the Meilisearch instance within your application by setting your `MEILISEARCH_HOST` environment variable to `http://meilisearch:7700`. +If you chose to install the [Meilisearch](https://www.meilisearch.com) service when installing Sail, your application's `docker-compose.yml` file will contain an entry for this powerful search engine that is integrated with [Laravel Scout](/docs/{{version}}/scout). Once you have started your containers, you may connect to the Meilisearch instance within your application by setting your `MEILISEARCH_HOST` environment variable to `http://meilisearch:7700`. From your local machine, you may access Meilisearch's web based administration panel by navigating to `http://localhost:7700` in your web browser. ### Typesense -If you chose to install the [Typesense](https://typesense.org) service when installing Sail, your application's `docker-compose.yml` file will contain an entry for this lightning fast, open-source search-engine that is natively integrated with [Laravel Scout](/docs/{{version}}/scout#typesense). Once you have started your containers, you may connect to the Typesense instance within your application by setting the following environment variables: +If you chose to install the [Typesense](https://typesense.org) service when installing Sail, your application's `docker-compose.yml` file will contain an entry for this lightning fast, open-source search engine that is natively integrated with [Laravel Scout](/docs/{{version}}/scout#typesense). Once you have started your containers, you may connect to the Typesense instance within your application by setting the following environment variables: ```ini TYPESENSE_HOST=typesense @@ -352,11 +378,11 @@ sail dusk #### Selenium on Apple Silicon -If your local machine contains an Apple Silicon chip, your `selenium` service must use the `seleniarm/standalone-chromium` image: +If your local machine contains an Apple Silicon chip, your `selenium` service must use the `selenium/standalone-chromium` image: ```yaml selenium: - image: 'seleniarm/standalone-chromium' + image: 'selenium/standalone-chromium' extra_hosts: - 'host.docker.internal:host-gateway' volumes: @@ -381,7 +407,7 @@ When Sail is running, you may access the Mailpit web interface at: http://localh ## Container CLI -Sometimes you may wish to start a Bash session within your application's container. You may use the `shell` command to connect to your application's container, allowing you to inspect its files and installed services as well execute arbitrary shell commands within the container: +Sometimes you may wish to start a Bash session within your application's container. You may use the `shell` command to connect to your application's container, allowing you to inspect its files and installed services as well as execute arbitrary shell commands within the container: ```shell sail shell @@ -398,9 +424,12 @@ sail tinker ## PHP Versions -Sail currently supports serving your application via PHP 8.3, 8.2, 8.1, or PHP 8.0. The default PHP version used by Sail is currently PHP 8.3. To change the PHP version that is used to serve your application, you should update the `build` definition of the `laravel.test` container in your application's `docker-compose.yml` file: +Sail currently supports serving your application via PHP 8.4, 8.3, 8.2, 8.1, or PHP 8.0. The default PHP version used by Sail is currently PHP 8.4. To change the PHP version that is used to serve your application, you should update the `build` definition of the `laravel.test` container in your application's `docker-compose.yml` file: ```yaml +# PHP 8.4 +context: ./vendor/laravel/sail/runtimes/8.4 + # PHP 8.3 context: ./vendor/laravel/sail/runtimes/8.3 @@ -460,9 +489,7 @@ sail share When sharing your site via the `share` command, you should configure your application's trusted proxies using the `trustProxies` middleware method in your application's `bootstrap/app.php` file. Otherwise, URL generation helpers such as `url` and `route` will be unable to determine the correct HTTP host that should be used during URL generation: ->withMiddleware(function (Middleware $middleware) { - $middleware->trustProxies(at: [ - '*', - ]); + $middleware->trustProxies(at: '*'); }) If you would like to choose the subdomain for your shared site, you may provide the `subdomain` option when executing the `share` command: @@ -477,26 +504,49 @@ sail share --subdomain=my-sail-site ## Debugging With Xdebug -Laravel Sail's Docker configuration includes support for [Xdebug](https://xdebug.org/), a popular and powerful debugger for PHP. In order to enable Xdebug, you will need to add a few variables to your application's `.env` file to [configure Xdebug](https://xdebug.org/docs/step_debug#mode). To enable Xdebug you must set the appropriate mode(s) before starting Sail: +Laravel Sail's Docker configuration includes support for [Xdebug](https://xdebug.org/), a popular and powerful debugger for PHP. To enable Xdebug, ensure you have [published your Sail configuration](#sail-customization). Then, add the following variables to your application's `.env` file to configure Xdebug: ```ini SAIL_XDEBUG_MODE=develop,debug,coverage ``` -#### Linux Host IP Configuration +Next, ensure that your published `php.ini` file includes the following configuration so that Xdebug is activated in the specified modes: -Internally, the `XDEBUG_CONFIG` environment variable is defined as `client_host=host.docker.internal` so that Xdebug will be properly configured for Mac and Windows (WSL2). If your local machine is running Linux, you should ensure that you are running Docker Engine 17.06.0+ and Compose 1.16.0+. Otherwise, you will need to manually define this environment variable as shown below. +```ini +[xdebug] +xdebug.mode=${XDEBUG_MODE} +``` -First, you should determine the correct host IP address to add to the environment variable by running the following command. Typically, the `` should be the name of the container that serves your application and often ends with `_laravel.test_1`: +After modifying the `php.ini` file, remember to rebuild your Docker images so that your changes to the `php.ini` file take effect: ```shell -docker inspect -f {{range.NetworkSettings.Networks}}{{.Gateway}}{{end}} +sail build --no-cache +``` + +#### Linux Host IP Configuration + +Internally, the `XDEBUG_CONFIG` environment variable is defined as `client_host=host.docker.internal` so that Xdebug will be properly configured for Mac and Windows (WSL2). If your local machine is running Linux and you're using Docker 20.10+, `host.docker.internal` is available, and no manual configuration is required. + +For Docker versions older than 20.10, `host.docker.internal` is not supported on Linux, and you will need to manually define the host IP. To do this, configure a static IP for your container by defining a custom network in your `docker-compose.yml` file: + +```yaml +networks: + custom_network: + ipam: + config: + - subnet: 172.20.0.0/16 + +services: + laravel.test: + networks: + custom_network: + ipv4_address: 172.20.0.2 ``` -Once you have obtained the correct host IP address, you should define the `SAIL_XDEBUG_CONFIG` variable within your application's `.env` file: +Once you have set the static IP, define the SAIL_XDEBUG_CONFIG variable within your application's .env file: ```ini -SAIL_XDEBUG_CONFIG="client_host=" +SAIL_XDEBUG_CONFIG="client_host=172.20.0.2" ``` @@ -517,7 +567,7 @@ sail debug migrate To debug your application while interacting with the application via a web browser, follow the [instructions provided by Xdebug](https://xdebug.org/docs/step_debug#web-application) for initiating an Xdebug session from the web browser. -If you're using PhpStorm, please review JetBrain's documentation regarding [zero-configuration debugging](https://www.jetbrains.com/help/phpstorm/zero-configuration-debugging.html). +If you're using PhpStorm, please review JetBrains' documentation regarding [zero-configuration debugging](https://www.jetbrains.com/help/phpstorm/zero-configuration-debugging.html). > [!WARNING] > Laravel Sail relies on `artisan serve` to serve your application. The `artisan serve` command only accepts the `XDEBUG_CONFIG` and `XDEBUG_MODE` variables as of Laravel version 8.53.0. Older versions of Laravel (8.52.0 and below) do not support these variables and will not accept debug connections. diff --git a/sanctum.md b/sanctum.md index b3080fad3d6..06e1f17f5f2 100644 --- a/sanctum.md +++ b/sanctum.md @@ -42,7 +42,7 @@ Laravel Sanctum offers this feature by storing user API tokens in a single datab #### SPA Authentication -Second, Sanctum exists to offer a simple way to authenticate single page applications (SPAs) that need to communicate with a Laravel powered API. These SPAs might exist in the same repository as your Laravel application or might be an entirely separate repository, such as a SPA created using Vue CLI or a Next.js application. +Second, Sanctum exists to offer a simple way to authenticate single page applications (SPAs) that need to communicate with a Laravel powered API. These SPAs might exist in the same repository as your Laravel application or might be an entirely separate repository, such as an SPA created using Next.js or Nuxt. For this feature, Sanctum does not use tokens of any kind. Instead, Sanctum uses Laravel's built-in cookie based session authentication services. Typically, Sanctum utilizes Laravel's `web` authentication guard to accomplish this. This provides the benefits of CSRF protection, session authentication, as well as protects against leakage of the authentication credentials via XSS. @@ -60,7 +60,7 @@ You may install Laravel Sanctum via the `install:api` Artisan command: php artisan install:api ``` -Next, if you plan to utilize Sanctum to authenticate a SPA, please refer to the [SPA Authentication](#spa-authentication) section of this documentation. +Next, if you plan to utilize Sanctum to authenticate an SPA, please refer to the [SPA Authentication](#spa-authentication) section of this documentation. ## Configuration @@ -133,12 +133,16 @@ Sanctum allows you to assign "abilities" to tokens. Abilities serve a similar pu return $user->createToken('token-name', ['server:update'])->plainTextToken; -When handling an incoming request authenticated by Sanctum, you may determine if the token has a given ability using the `tokenCan` method: +When handling an incoming request authenticated by Sanctum, you may determine if the token has a given ability using the `tokenCan` or `tokenCant` methods: if ($user->tokenCan('server:update')) { // ... } + if ($user->tokenCant('server:update')) { + // ... + } + #### Token Ability Middleware @@ -267,7 +271,7 @@ Next, you should instruct Laravel that incoming requests from your SPA can authe #### CORS and Cookies -If you are having trouble authenticating with your application from a SPA that executes on a separate subdomain, you have likely misconfigured your CORS (Cross-Origin Resource Sharing) or session cookie settings. +If you are having trouble authenticating with your application from an SPA that executes on a separate subdomain, you have likely misconfigured your CORS (Cross-Origin Resource Sharing) or session cookie settings. The `config/cors.php` configuration file is not published by default. If you need to customize Laravel's CORS options, you should publish the complete `cors` configuration file using the `config:publish` Artisan command: @@ -302,7 +306,7 @@ axios.get('/sanctum/csrf-cookie').then(response => { }); ``` -During this request, Laravel will set an `XSRF-TOKEN` cookie containing the current CSRF token. This token should then be passed in an `X-XSRF-TOKEN` header on subsequent requests, which some HTTP client libraries like Axios and the Angular HttpClient will do automatically for you. If your JavaScript HTTP library does not set the value for you, you will need to manually set the `X-XSRF-TOKEN` header to match the value of the `XSRF-TOKEN` cookie that is set by this route. +During this request, Laravel will set an `XSRF-TOKEN` cookie containing the current CSRF token. This token should then be URL decoded and passed in an `X-XSRF-TOKEN` header on subsequent requests, which some HTTP client libraries like Axios and the Angular HttpClient will do automatically for you. If your JavaScript HTTP library does not set the value for you, you will need to manually set the `X-XSRF-TOKEN` header to match the URL decoded value of the `XSRF-TOKEN` cookie that is set by this route. #### Logging In diff --git a/scheduling.md b/scheduling.md index ec35a5bc564..0cb90589a6b 100644 --- a/scheduling.md +++ b/scheduling.md @@ -11,6 +11,7 @@ - [Running Tasks on One Server](#running-tasks-on-one-server) - [Background Tasks](#background-tasks) - [Maintenance Mode](#maintenance-mode) + - [Schedule Groups](#schedule-groups) - [Running the Scheduler](#running-the-scheduler) - [Sub-Minute Scheduled Tasks](#sub-minute-scheduled-tasks) - [Running the Scheduler Locally](#running-the-scheduler-locally) @@ -120,46 +121,46 @@ We've already seen a few examples of how you may configure a task to run at spec
-Method | Description -------------- | ------------- -`->cron('* * * * *');` | Run the task on a custom cron schedule -`->everySecond();` | Run the task every second -`->everyTwoSeconds();` | Run the task every two seconds -`->everyFiveSeconds();` | Run the task every five seconds -`->everyTenSeconds();` | Run the task every ten seconds -`->everyFifteenSeconds();` | Run the task every fifteen seconds -`->everyTwentySeconds();` | Run the task every twenty seconds -`->everyThirtySeconds();` | Run the task every thirty seconds -`->everyMinute();` | Run the task every minute -`->everyTwoMinutes();` | Run the task every two minutes -`->everyThreeMinutes();` | Run the task every three minutes -`->everyFourMinutes();` | Run the task every four minutes -`->everyFiveMinutes();` | Run the task every five minutes -`->everyTenMinutes();` | Run the task every ten minutes -`->everyFifteenMinutes();` | Run the task every fifteen minutes -`->everyThirtyMinutes();` | Run the task every thirty minutes -`->hourly();` | Run the task every hour -`->hourlyAt(17);` | Run the task every hour at 17 minutes past the hour -`->everyOddHour($minutes = 0);` | Run the task every odd hour -`->everyTwoHours($minutes = 0);` | Run the task every two hours -`->everyThreeHours($minutes = 0);` | Run the task every three hours -`->everyFourHours($minutes = 0);` | Run the task every four hours -`->everySixHours($minutes = 0);` | Run the task every six hours -`->daily();` | Run the task every day at midnight -`->dailyAt('13:00');` | Run the task every day at 13:00 -`->twiceDaily(1, 13);` | Run the task daily at 1:00 & 13:00 -`->twiceDailyAt(1, 13, 15);` | Run the task daily at 1:15 & 13:15 -`->weekly();` | Run the task every Sunday at 00:00 -`->weeklyOn(1, '8:00');` | Run the task every week on Monday at 8:00 -`->monthly();` | Run the task on the first day of every month at 00:00 -`->monthlyOn(4, '15:00');` | Run the task every month on the 4th at 15:00 -`->twiceMonthly(1, 16, '13:00');` | Run the task monthly on the 1st and 16th at 13:00 -`->lastDayOfMonth('15:00');` | Run the task on the last day of the month at 15:00 -`->quarterly();` | Run the task on the first day of every quarter at 00:00 -`->quarterlyOn(4, '14:00');` | Run the task every quarter on the 4th at 14:00 -`->yearly();` | Run the task on the first day of every year at 00:00 -`->yearlyOn(6, 1, '17:00');` | Run the task every year on June 1st at 17:00 -`->timezone('America/New_York');` | Set the timezone for the task +| Method | Description | +| ---------------------------------- | -------------------------------------------------------- | +| `->cron('* * * * *');` | Run the task on a custom cron schedule. | +| `->everySecond();` | Run the task every second. | +| `->everyTwoSeconds();` | Run the task every two seconds. | +| `->everyFiveSeconds();` | Run the task every five seconds. | +| `->everyTenSeconds();` | Run the task every ten seconds. | +| `->everyFifteenSeconds();` | Run the task every fifteen seconds. | +| `->everyTwentySeconds();` | Run the task every twenty seconds. | +| `->everyThirtySeconds();` | Run the task every thirty seconds. | +| `->everyMinute();` | Run the task every minute. | +| `->everyTwoMinutes();` | Run the task every two minutes. | +| `->everyThreeMinutes();` | Run the task every three minutes. | +| `->everyFourMinutes();` | Run the task every four minutes. | +| `->everyFiveMinutes();` | Run the task every five minutes. | +| `->everyTenMinutes();` | Run the task every ten minutes. | +| `->everyFifteenMinutes();` | Run the task every fifteen minutes. | +| `->everyThirtyMinutes();` | Run the task every thirty minutes. | +| `->hourly();` | Run the task every hour. | +| `->hourlyAt(17);` | Run the task every hour at 17 minutes past the hour. | +| `->everyOddHour($minutes = 0);` | Run the task every odd hour. | +| `->everyTwoHours($minutes = 0);` | Run the task every two hours. | +| `->everyThreeHours($minutes = 0);` | Run the task every three hours. | +| `->everyFourHours($minutes = 0);` | Run the task every four hours. | +| `->everySixHours($minutes = 0);` | Run the task every six hours. | +| `->daily();` | Run the task every day at midnight. | +| `->dailyAt('13:00');` | Run the task every day at 13:00. | +| `->twiceDaily(1, 13);` | Run the task daily at 1:00 & 13:00. | +| `->twiceDailyAt(1, 13, 15);` | Run the task daily at 1:15 & 13:15. | +| `->weekly();` | Run the task every Sunday at 00:00. | +| `->weeklyOn(1, '8:00');` | Run the task every week on Monday at 8:00. | +| `->monthly();` | Run the task on the first day of every month at 00:00. | +| `->monthlyOn(4, '15:00');` | Run the task every month on the 4th at 15:00. | +| `->twiceMonthly(1, 16, '13:00');` | Run the task monthly on the 1st and 16th at 13:00. | +| `->lastDayOfMonth('15:00');` | Run the task on the last day of the month at 15:00. | +| `->quarterly();` | Run the task on the first day of every quarter at 00:00. | +| `->quarterlyOn(4, '14:00');` | Run the task every quarter on the 4th at 14:00. | +| `->yearly();` | Run the task on the first day of every year at 00:00. | +| `->yearlyOn(6, 1, '17:00');` | Run the task every year on June 1st at 17:00. | +| `->timezone('America/New_York');` | Set the timezone for the task. |
@@ -183,22 +184,22 @@ A list of additional schedule constraints may be found below:
-Method | Description -------------- | ------------- -`->weekdays();` | Limit the task to weekdays -`->weekends();` | Limit the task to weekends -`->sundays();` | Limit the task to Sunday -`->mondays();` | Limit the task to Monday -`->tuesdays();` | Limit the task to Tuesday -`->wednesdays();` | Limit the task to Wednesday -`->thursdays();` | Limit the task to Thursday -`->fridays();` | Limit the task to Friday -`->saturdays();` | Limit the task to Saturday -`->days(array\|mixed);` | Limit the task to specific days -`->between($startTime, $endTime);` | Limit the task to run between start and end times -`->unlessBetween($startTime, $endTime);` | Limit the task to not run between start and end times -`->when(Closure);` | Limit the task based on a truth test -`->environments($env);` | Limit the task to specific environments +| Method | Description | +| ---------------------------------------- | ------------------------------------------------------ | +| `->weekdays();` | Limit the task to weekdays. | +| `->weekends();` | Limit the task to weekends. | +| `->sundays();` | Limit the task to Sunday. | +| `->mondays();` | Limit the task to Monday. | +| `->tuesdays();` | Limit the task to Tuesday. | +| `->wednesdays();` | Limit the task to Wednesday. | +| `->thursdays();` | Limit the task to Thursday. | +| `->fridays();` | Limit the task to Friday. | +| `->saturdays();` | Limit the task to Saturday. | +| `->days(array\|mixed);` | Limit the task to specific days. | +| `->between($startTime, $endTime);` | Limit the task to run between start and end times. | +| `->unlessBetween($startTime, $endTime);` | Limit the task to not run between start and end times. | +| `->when(Closure);` | Limit the task based on a truth test. | +| `->environments($env);` | Limit the task to specific environments. |
@@ -343,7 +344,6 @@ Schedule::call(fn () => User::resetApiRequestCount()) ->onOneServer(); ``` - ### Background Tasks @@ -365,6 +365,25 @@ Your application's scheduled tasks will not run when the application is in [main Schedule::command('emails:send')->evenInMaintenanceMode(); + +### Schedule Groups + +When defining multiple scheduled tasks with similar configurations, you can use Laravel’s task grouping feature to avoid repeating the same settings for each task. Grouping tasks simplifies your code and ensures consistency across related tasks. + +To create a group of scheduled tasks, invoke the desired task configuration methods, followed by the `group` method. The `group` method accepts a closure that is responsible for defining the tasks that share the specified configuration: + +```php +use Illuminate\Support\Facades\Schedule; + +Schedule::daily() + ->onOneServer() + ->timezone('America/New_York') + ->group(function () { + Schedule::command('emails:send --force'); + Schedule::command('emails:prune'); + }); +``` + ## Running the Scheduler @@ -389,7 +408,7 @@ On most operating systems, cron jobs are limited to running a maximum of once pe When sub-minute tasks are defined within your application, the `schedule:run` command will continue running until the end of the current minute instead of exiting immediately. This allows the command to invoke all required sub-minute tasks throughout the minute. -Since sub-minute tasks that take longer than expected to run could delay the execution of later sub-minute tasks, it is recommend that all sub-minute tasks dispatch queued jobs or background commands to handle the actual task processing: +Since sub-minute tasks that take longer than expected to run could delay the execution of later sub-minute tasks, it is recommended that all sub-minute tasks dispatch queued jobs or background commands to handle the actual task processing: use App\Jobs\DeleteRecentUsers; @@ -411,7 +430,7 @@ php artisan schedule:interrupt ### Running the Scheduler Locally -Typically, you would not add a scheduler cron entry to your local development machine. Instead, you may use the `schedule:work` Artisan command. This command will run in the foreground and invoke the scheduler every minute until you terminate the command: +Typically, you would not add a scheduler cron entry to your local development machine. Instead, you may use the `schedule:work` Artisan command. This command will run in the foreground and invoke the scheduler every minute until you terminate the command. When sub-minute tasks are defined, the scheduler will continue running within each minute to process those tasks: ```shell php artisan schedule:work @@ -500,29 +519,38 @@ Using the `pingBefore` and `thenPing` methods, the scheduler can automatically p ->pingBefore($url) ->thenPing($url); -The `pingBeforeIf` and `thenPingIf` methods may be used to ping a given URL only if a given condition is `true`: +The `pingOnSuccess` and `pingOnFailure` methods may be used to ping a given URL only if the task succeeds or fails. A failure indicates that the scheduled Artisan or system command terminated with a non-zero exit code: Schedule::command('emails:send') ->daily() - ->pingBeforeIf($condition, $url) - ->thenPingIf($condition, $url); + ->pingOnSuccess($successUrl) + ->pingOnFailure($failureUrl); -The `pingOnSuccess` and `pingOnFailure` methods may be used to ping a given URL only if the task succeeds or fails. A failure indicates that the scheduled Artisan or system command terminated with a non-zero exit code: +The `pingBeforeIf`,`thenPingIf`,`pingOnSuccessIf`, and `pingOnFailureIf` methods may be used to ping a given URL only if a given condition is `true`: Schedule::command('emails:send') ->daily() - ->pingOnSuccess($successUrl) - ->pingOnFailure($failureUrl); + ->pingBeforeIf($condition, $url) + ->thenPingIf($condition, $url); + + Schedule::command('emails:send') + ->daily() + ->pingOnSuccessIf($condition, $successUrl) + ->pingOnFailureIf($condition, $failureUrl); ## Events Laravel dispatches a variety of [events](/docs/{{version}}/events) during the scheduling process. You may [define listeners](/docs/{{version}}/events) for any of the following events: -Event Name | -------------- | -`Illuminate\Console\Events\ScheduledTaskStarting` | -`Illuminate\Console\Events\ScheduledTaskFinished` | -`Illuminate\Console\Events\ScheduledBackgroundTaskFinished` | -`Illuminate\Console\Events\ScheduledTaskSkipped` | -`Illuminate\Console\Events\ScheduledTaskFailed` | +
+ +| Event Name | +| --- | +| `Illuminate\Console\Events\ScheduledTaskStarting` | +| `Illuminate\Console\Events\ScheduledTaskFinished` | +| `Illuminate\Console\Events\ScheduledBackgroundTaskFinished` | +| `Illuminate\Console\Events\ScheduledTaskSkipped` | +| `Illuminate\Console\Events\ScheduledTaskFailed` | + +
diff --git a/scout.md b/scout.md index 535fb5bb18b..4812de51f48 100644 --- a/scout.md +++ b/scout.md @@ -141,15 +141,15 @@ composer require typesense/typesense-php Then, set the `SCOUT_DRIVER` environment variable as well as your Typesense host and API key credentials within your application's .env file: -```env +```ini SCOUT_DRIVER=typesense TYPESENSE_API_KEY=masterKey TYPESENSE_HOST=localhost ``` -If needed, you may also specify your installation's port, path, and protocol: +If you are using [Laravel Sail](/docs/{{version}}/sail), you may need to adjust the `TYPESENSE_HOST` environment variable to match the Docker container name. You may also optionally specify your installation's port, path, and protocol: -```env +```ini TYPESENSE_PORT=8108 TYPESENSE_PATH= TYPESENSE_PROTOCOL=http @@ -177,7 +177,7 @@ public function toSearchableArray() } ``` -You should also define your Typesense collection schemas in your application's `config/scout.php` file. A collection schema describes the data types of each field that is searchable via Typesense. For more information on all available schema options, please consult the [Typesense documentation](https://typesense.org/docs/latest/api/collections.html#schema-parameters). +You should also define your Typesense collection schemas in your application's `config/scout.php` file. A collection schema describes the data types of each field that is searchable via Typesense. For more information on all available schema options, please consult the [Typesense documentation](https://typesense.org/docs/latest/api/collections.html#schema-parameters). If you need to change your Typesense collection's schema after it has been defined, you may either run `scout:flush` and `scout:import`, which will delete all existing indexed data and recreate the schema. Or, you may use Typesense's API to modify the collection's schema without removing any indexed data. @@ -595,6 +595,10 @@ Or, if you already have a collection of Eloquent models in memory, you may call $orders->unsearchable(); +To remove all of the model records from their corresponding index, you may invoke the `removeAllFromSearch` method: + + Order::removeAllFromSearch(); + ### Pausing Indexing diff --git a/seeding.md b/seeding.md index 4dd70fab4f2..400bbcb308d 100644 --- a/seeding.md +++ b/seeding.md @@ -135,7 +135,7 @@ You may also seed your database using the `migrate:fresh` command in combination ```shell php artisan migrate:fresh --seed -php artisan migrate:fresh --seed --seeder=UserSeeder +php artisan migrate:fresh --seed --seeder=UserSeeder ``` diff --git a/session.md b/session.md index c01fddf0b7a..7daa7d74481 100644 --- a/session.md +++ b/session.md @@ -185,7 +185,7 @@ The `pull` method will retrieve and delete an item from the session in a single $value = $request->session()->pull('key', 'default'); - + #### Incrementing and Decrementing Session Values If your session data contains an integer you wish to increment or decrement, you may use the `increment` and `decrement` methods: @@ -245,7 +245,7 @@ If you need to regenerate the session ID and remove all data from the session in ## Session Blocking > [!WARNING] -> To utilize session blocking, your application must be using a cache driver that supports [atomic locks](/docs/{{version}}/cache#atomic-locks). Currently, those cache drivers include the `memcached`, `dynamodb`, `redis`, `database`, `file`, and `array` drivers. In addition, you may not use the `cookie` session driver. +> To utilize session blocking, your application must be using a cache driver that supports [atomic locks](/docs/{{version}}/cache#atomic-locks). Currently, those cache drivers include the `memcached`, `dynamodb`, `redis`, `mongodb` (included in the official `mongodb/laravel-mongodb` package), `database`, `file`, and `array` drivers. In addition, you may not use the `cookie` session driver. By default, Laravel allows requests using the same session to execute concurrently. So, for example, if you use a JavaScript HTTP library to make two HTTP requests to your application, they will both execute at the same time. For many applications, this is not a problem; however, session data loss can occur in a small subset of applications that make concurrent requests to two different application endpoints which both write data to the session. @@ -291,10 +291,9 @@ If none of the existing session drivers fit your application's needs, Laravel ma public function gc($lifetime) {} } -> [!NOTE] -> Laravel does not ship with a directory to contain your extensions. You are free to place them anywhere you like. In this example, we have created an `Extensions` directory to house the `MongoSessionHandler`. +Since Laravel does not include a default directory to house your extensions. You are free to place them anywhere you like. In this example, we have created an `Extensions` directory to house the `MongoSessionHandler`. -Since the purpose of these methods is not readily understandable, let's quickly cover what each of the methods do: +Since the purpose of these methods is not readily understandable, here is an overview of the purpose of each method:
diff --git a/socialite.md b/socialite.md index ae937cf24eb..e4689ceab92 100644 --- a/socialite.md +++ b/socialite.md @@ -15,7 +15,7 @@ ## Introduction -In addition to typical, form based authentication, Laravel also provides a simple, convenient way to authenticate with OAuth providers using [Laravel Socialite](https://github.com/laravel/socialite). Socialite currently supports authentication via Facebook, Twitter, LinkedIn, Google, GitHub, GitLab, Bitbucket, and Slack. +In addition to typical, form based authentication, Laravel also provides a simple, convenient way to authenticate with OAuth providers using [Laravel Socialite](https://github.com/laravel/socialite). Socialite currently supports authentication via Facebook, X, LinkedIn, Google, GitHub, GitLab, Bitbucket, and Slack. > [!NOTE] > Adapters for other platforms are available via the community driven [Socialite Providers](https://socialiteproviders.com/) website. @@ -39,7 +39,7 @@ When upgrading to a new major version of Socialite, it's important that you care Before using Socialite, you will need to add credentials for the OAuth providers your application utilizes. Typically, these credentials may be retrieved by creating a "developer application" within the dashboard of the service you will be authenticating with. -These credentials should be placed in your application's `config/services.php` configuration file, and should use the key `facebook`, `twitter` (OAuth 1.0), `twitter-oauth-2` (OAuth 2.0), `linkedin-openid`, `google`, `github`, `gitlab`, `bitbucket`, or `slack`, depending on the providers your application requires: +These credentials should be placed in your application's `config/services.php` configuration file, and should use the key `facebook`, `x`, `linkedin-openid`, `google`, `github`, `gitlab`, `bitbucket`, `slack`, or `slack-openid`, depending on the providers your application requires: 'github' => [ 'client_id' => env('GITHUB_CLIENT_ID'), @@ -189,7 +189,7 @@ Differing properties and methods may be available on this object depending on wh }); -#### Retrieving User Details From a Token (OAuth2) +#### Retrieving User Details From a Token If you already have a valid access token for a user, you can retrieve their user details using Socialite's `userFromToken` method: @@ -199,15 +199,6 @@ If you already have a valid access token for a user, you can retrieve their user If you are using Facebook Limited Login via an iOS application, Facebook will return an OIDC token instead of an access token. Like an access token, the OIDC token can be provided to the `userFromToken` method in order to retrieve user details. - -#### Retrieving User Details From a Token and Secret (OAuth1) - -If you already have a valid token and secret for a user, you can retrieve their user details using Socialite's `userFromTokenAndSecret` method: - - use Laravel\Socialite\Facades\Socialite; - - $user = Socialite::driver('twitter')->userFromTokenAndSecret($token, $secret); - #### Stateless Authentication @@ -216,6 +207,3 @@ The `stateless` method may be used to disable session state verification. This i use Laravel\Socialite\Facades\Socialite; return Socialite::driver('google')->stateless()->user(); - -> [!WARNING] -> Stateless authentication is not available for the Twitter OAuth 1.0 driver. diff --git a/strings.md b/strings.md index 102f67d31f3..f5b1b957326 100644 --- a/strings.md +++ b/strings.md @@ -43,8 +43,12 @@ Laravel includes a variety of functions for manipulating string values. Many of [Str::betweenFirst](#method-str-between-first) [Str::camel](#method-camel-case) [Str::charAt](#method-char-at) +[Str::chopStart](#method-str-chop-start) +[Str::chopEnd](#method-str-chop-end) [Str::contains](#method-str-contains) [Str::containsAll](#method-str-contains-all) +[Str::doesntContain](#method-str-doesnt-contain) +[Str::deduplicate](#method-deduplicate) [Str::endsWith](#method-ends-with) [Str::excerpt](#method-excerpt) [Str::finish](#method-str-finish) @@ -96,7 +100,7 @@ Laravel includes a variety of functions for manipulating string values. Many of [Str::take](#method-take) [Str::title](#method-title-case) [Str::toBase64](#method-str-to-base64) -[Str::toHtmlString](#method-str-to-html-string) +[Str::transliterate](#method-str-transliterate) [Str::trim](#method-str-trim) [Str::ltrim](#method-str-ltrim) [Str::rtrim](#method-str-rtrim) @@ -134,12 +138,15 @@ Laravel includes a variety of functions for manipulating string values. Many of [camel](#method-fluent-str-camel) [charAt](#method-fluent-str-char-at) [classBasename](#method-fluent-str-class-basename) +[chopStart](#method-fluent-str-chop-start) +[chopEnd](#method-fluent-str-chop-end) [contains](#method-fluent-str-contains) [containsAll](#method-fluent-str-contains-all) +[deduplicate](#method-fluent-str-deduplicate) [dirname](#method-fluent-str-dirname) [endsWith](#method-fluent-str-ends-with) -[excerpt](#method-fluent-str-excerpt) [exactly](#method-fluent-str-exactly) +[excerpt](#method-fluent-str-excerpt) [explode](#method-fluent-str-explode) [finish](#method-fluent-str-finish) [headline](#method-fluent-str-headline) @@ -197,6 +204,8 @@ Laravel includes a variety of functions for manipulating string values. Many of [test](#method-fluent-str-test) [title](#method-fluent-str-title) [toBase64](#method-fluent-str-to-base64) +[toHtmlString](#method-fluent-str-to-html-string) +[transliterate](#method-fluent-str-transliterate) [trim](#method-fluent-str-trim) [ltrim](#method-fluent-str-ltrim) [rtrim](#method-fluent-str-rtrim) @@ -220,6 +229,7 @@ Laravel includes a variety of functions for manipulating string values. Many of [whenTest](#method-fluent-str-when-test) [wordCount](#method-fluent-str-word-count) [words](#method-fluent-str-words) +[wrap](#method-fluent-str-wrap)
@@ -366,7 +376,6 @@ The `Str::camel` method converts the given string to `camelCase`: // 'fooBar' - #### `Str::charAt()` {.collection-method} The `Str::charAt` method returns the character at the specified index. If the index is out of bounds, `false` is returned: @@ -377,10 +386,48 @@ The `Str::charAt` method returns the character at the specified index. If the in // 's' + +#### `Str::chopStart()` {.collection-method} + +The `Str::chopStart` method removes the first occurrence of the given value only if the value appears at the start of the string: + + use Illuminate\Support\Str; + + $url = Str::chopStart('https://laravel.com', 'https://'); + + // 'laravel.com' + +You may also pass an array as the second argument. If the string starts with any of the values in the array then that value will be removed from string: + + use Illuminate\Support\Str; + + $url = Str::chopStart('http://laravel.com', ['https://', 'http://']); + + // 'laravel.com' + + +#### `Str::chopEnd()` {.collection-method} + +The `Str::chopEnd` method removes the last occurrence of the given value only if the value appears at the end of the string: + + use Illuminate\Support\Str; + + $url = Str::chopEnd('app/Models/Photograph.php', '.php'); + + // 'app/Models/Photograph' + +You may also pass an array as the second argument. If the string ends with any of the values in the array then that value will be removed from string: + + use Illuminate\Support\Str; + + $url = Str::chopEnd('laravel.com/index.php', ['/index.html', '/index.php']); + + // 'laravel.com' + #### `Str::contains()` {.collection-method} -The `Str::contains` method determines if the given string contains the given value. This method is case sensitive: +The `Str::contains` method determines if the given string contains the given value. By default this method is case sensitive: use Illuminate\Support\Str; @@ -396,6 +443,14 @@ You may also pass an array of values to determine if the given string contains a // true +You may disable case sensitivity by setting the `ignoreCase` argument to `true`: + + use Illuminate\Support\Str; + + $contains = Str::contains('This is my name', 'MY', ignoreCase: true); + + // true + #### `Str::containsAll()` {.collection-method} @@ -407,6 +462,60 @@ The `Str::containsAll` method determines if the given string contains all of the // true +You may disable case sensitivity by setting the `ignoreCase` argument to `true`: + + use Illuminate\Support\Str; + + $containsAll = Str::containsAll('This is my name', ['MY', 'NAME'], ignoreCase: true); + + // true + + +#### `Str::doesntContain()` {.collection-method} + +The `Str::doesntContain` method determines if the given string doesn't contain the given value. By default this method is case sensitive: + + use Illuminate\Support\Str; + + $doesntContain = Str::doesntContain('This is name', 'my'); + + // true + +You may also pass an array of values to determine if the given string doesn't contain any of the values in the array: + + use Illuminate\Support\Str; + + $doesntContain = Str::doesntContain('This is name', ['my', 'foo']); + + // true + +You may disable case sensitivity by setting the `ignoreCase` argument to `true`: + + use Illuminate\Support\Str; + + $doesntContain = Str::doesntContain('This is name', 'MY', ignoreCase: true); + + // true + + +#### `Str::deduplicate()` {.collection-method} + +The `Str::deduplicate` method replaces consecutive instances of a character with a single instance of that character in the given string. By default, the method deduplicates spaces: + + use Illuminate\Support\Str; + + $result = Str::deduplicate('The Laravel Framework'); + + // The Laravel Framework + +You may specify a different character to deduplicate by passing it in as the second argument to the method: + + use Illuminate\Support\Str; + + $result = Str::deduplicate('The---Laravel---Framework', '-'); + + // The-Laravel-Framework + #### `Str::endsWith()` {.collection-method} @@ -418,7 +527,6 @@ The `Str::endsWith` method determines if the given string ends with the given va // true - You may also pass an array of values to determine if the given string ends with any of the values in the array: use Illuminate\Support\Str; @@ -503,12 +611,12 @@ The `Str::inlineMarkdown` method converts GitHub flavored Markdown into inline H By default, Markdown supports raw HTML, which will expose Cross-Site Scripting (XSS) vulnerabilities when used with raw user input. As per the [CommonMark Security documentation](https://commonmark.thephpleague.com/security/), you may use the `html_input` option to either escape or strip raw HTML, and the `allow_unsafe_links` option to specify whether to allow unsafe links. If you need to allow some raw HTML, you should pass your compiled Markdown through an HTML Purifier: use Illuminate\Support\Str; - + Str::inlineMarkdown('Inject: ', [ 'html_input' => 'strip', 'allow_unsafe_links' => false, ]); - + // Inject: alert("Hello XSS!"); @@ -526,6 +634,14 @@ The `Str::is` method determines if a given string matches a given pattern. Aster // false +You may disable case sensitivity by setting the `ignoreCase` argument to `true`: + + use Illuminate\Support\Str; + + $matches = Str::is('*.jpg', 'photo.JPG', ignoreCase: true); + + // true + #### `Str::isAscii()` {.collection-method} @@ -655,12 +771,16 @@ The `Str::limit` method truncates the given string to the specified length: You may pass a third argument to the method to change the string that will be appended to the end of the truncated string: - use Illuminate\Support\Str; - $truncated = Str::limit('The quick brown fox jumps over the lazy dog', 20, ' (...)'); // The quick brown fox (...) +If you would like to preserve complete words when truncating the string, you may utilize the `preserveWords` argument. When this argument is `true`, the string will be truncated to the nearest complete word boundary: + + $truncated = Str::limit('The quick brown fox', 12, preserveWords: true); + + // The quick... + #### `Str::lower()` {.collection-method} @@ -1201,14 +1321,16 @@ The `Str::toBase64` method converts the given string to Base64: // TGFyYXZlbA== - -#### `Str::toHtmlString()` {.collection-method} + +#### `Str::transliterate()` {.collection-method} -The `Str::toHtmlString` method converts the string instance to an instance of `Illuminate\Support\HtmlString`, which may be displayed in Blade templates: +The `Str::transliterate` method will attempt to convert a given string into its closest ASCII representation: use Illuminate\Support\Str; - $htmlString = Str::of('Nuno Maduro')->toHtmlString(); + $email = Str::transliterate('ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ'); + + // 'test@laravel.com' #### `Str::trim()` {.collection-method} @@ -1284,7 +1406,7 @@ The `Str::ulid` method generates a ULID, which is a compact, time-ordered unique use Illuminate\Support\Str; return (string) Str::ulid(); - + // 01gd6r360bp37zj17nxb55yv40 If you would like to retrieve a `Illuminate\Support\Carbon` date instance representing the date and time that a given ULID was created, you may use the `createFromId` method provided by Laravel's Carbon integration: @@ -1587,10 +1709,48 @@ The `classBasename` method returns the class name of the given class with the cl // 'Baz' + +#### `chopStart` {.collection-method} + +The `chopStart` method removes the first occurrence of the given value only if the value appears at the start of the string: + + use Illuminate\Support\Str; + + $url = Str::of('https://laravel.com')->chopStart('https://'); + + // 'laravel.com' + +You may also pass an array. If the string starts with any of the values in the array then that value will be removed from string: + + use Illuminate\Support\Str; + + $url = Str::of('http://laravel.com')->chopStart(['https://', 'http://']); + + // 'laravel.com' + + +#### `chopEnd` {.collection-method} + +The `chopEnd` method removes the last occurrence of the given value only if the value appears at the end of the string: + + use Illuminate\Support\Str; + + $url = Str::of('https://laravel.com')->chopEnd('.com'); + + // 'https://laravel' + +You may also pass an array. If the string ends with any of the values in the array then that value will be removed from string: + + use Illuminate\Support\Str; + + $url = Str::of('http://laravel.com')->chopEnd(['.com', '.io']); + + // 'http://laravel' + #### `contains` {.collection-method} -The `contains` method determines if the given string contains the given value. This method is case sensitive: +The `contains` method determines if the given string contains the given value. By default this method is case sensitive: use Illuminate\Support\Str; @@ -1606,6 +1766,14 @@ You may also pass an array of values to determine if the given string contains a // true +You can disable case sensitivity by setting the `ignoreCase` argument to `true`: + + use Illuminate\Support\Str; + + $contains = Str::of('This is my name')->contains('MY', ignoreCase: true); + + // true + #### `containsAll` {.collection-method} @@ -1617,50 +1785,51 @@ The `containsAll` method determines if the given string contains all of the valu // true - -#### `dirname` {.collection-method} +You can disable case sensitivity by setting the `ignoreCase` argument to `true`: -The `dirname` method returns the parent directory portion of the given string: + use Illuminate\Support\Str; + + $containsAll = Str::of('This is my name')->containsAll(['MY', 'NAME'], ignoreCase: true); + + // true + + +#### `deduplicate` {.collection-method} + +The `deduplicate` method replaces consecutive instances of a character with a single instance of that character in the given string. By default, the method deduplicates spaces: use Illuminate\Support\Str; - $string = Str::of('/foo/bar/baz')->dirname(); + $result = Str::of('The Laravel Framework')->deduplicate(); - // '/foo/bar' + // The Laravel Framework -If necessary, you may specify how many directory levels you wish to trim from the string: +You may specify a different character to deduplicate by passing it in as the second argument to the method: use Illuminate\Support\Str; - $string = Str::of('/foo/bar/baz')->dirname(2); + $result = Str::of('The---Laravel---Framework')->deduplicate('-'); - // '/foo' + // The-Laravel-Framework - -#### `excerpt` {.collection-method} + +#### `dirname` {.collection-method} -The `excerpt` method extracts an excerpt from the string that matches the first instance of a phrase within that string: +The `dirname` method returns the parent directory portion of the given string: use Illuminate\Support\Str; - $excerpt = Str::of('This is my name')->excerpt('my', [ - 'radius' => 3 - ]); - - // '...is my na...' + $string = Str::of('/foo/bar/baz')->dirname(); -The `radius` option, which defaults to `100`, allows you to define the number of characters that should appear on each side of the truncated string. + // '/foo/bar' -In addition, you may use the `omission` option to change the string that will be prepended and appended to the truncated string: +If necessary, you may specify how many directory levels you wish to trim from the string: use Illuminate\Support\Str; - $excerpt = Str::of('This is my name')->excerpt('name', [ - 'radius' => 3, - 'omission' => '(...) ' - ]); + $string = Str::of('/foo/bar/baz')->dirname(2); - // '(...) my name' + // '/foo' #### `endsWith` {.collection-method} @@ -1696,6 +1865,32 @@ The `exactly` method determines if the given string is an exact match with anoth // true + +#### `excerpt` {.collection-method} + +The `excerpt` method extracts an excerpt from the string that matches the first instance of a phrase within that string: + + use Illuminate\Support\Str; + + $excerpt = Str::of('This is my name')->excerpt('my', [ + 'radius' => 3 + ]); + + // '...is my na...' + +The `radius` option, which defaults to `100`, allows you to define the number of characters that should appear on each side of the truncated string. + +In addition, you may use the `omission` option to change the string that will be prepended and appended to the truncated string: + + use Illuminate\Support\Str; + + $excerpt = Str::of('This is my name')->excerpt('name', [ + 'radius' => 3, + 'omission' => '(...) ' + ]); + + // '(...) my name' + #### `explode` {.collection-method} @@ -1811,7 +2006,6 @@ The `isEmpty` method determines if the given string is empty: The `isNotEmpty` method determines if the given string is not empty: - use Illuminate\Support\Str; $result = Str::of(' ')->trim()->isNotEmpty(); @@ -1912,7 +2106,6 @@ The `lcfirst` method returns the given string with the first character lowercase // foo Bar - #### `length` {.collection-method} @@ -1937,12 +2130,16 @@ The `limit` method truncates the given string to the specified length: You may also pass a second argument to change the string that will be appended to the end of the truncated string: - use Illuminate\Support\Str; - $truncated = Str::of('The quick brown fox jumps over the lazy dog')->limit(20, ' (...)'); // The quick brown fox (...) +If you would like to preserve complete words when truncating the string, you may utilize the `preserveWords` argument. When this argument is `true`, the string will be truncated to the nearest complete word boundary: + + $truncated = Str::of('The quick brown fox')->limit(12, preserveWords: true); + + // The quick... + #### `lower` {.collection-method} @@ -2031,7 +2228,7 @@ The `matchAll` method will return a collection containing the portions of a stri // collect(['bar', 'bar']) -If you specify a matching group within the expression, Laravel will return a collection of that group's matches: +If you specify a matching group within the expression, Laravel will return a collection of the first matching group's matches: use Illuminate\Support\Str; @@ -2531,7 +2728,7 @@ The `title` method converts the given string to `Title Case`: // A Nice Title Uses The Correct Case -#### `toBase64()` {.collection-method} +#### `toBase64` {.collection-method} The `toBase64` method converts the given string to Base64: @@ -2541,6 +2738,26 @@ The `toBase64` method converts the given string to Base64: // TGFyYXZlbA== + +#### `toHtmlString` {.collection-method} + +The `toHtmlString` method converts the given string to an instance of `Illuminate\Support\HtmlString`, which will not be escaped when rendered in Blade templates: + + use Illuminate\Support\Str; + + $htmlString = Str::of('Nuno Maduro')->toHtmlString(); + + +#### `transliterate` {.collection-method} + +The `transliterate` method will attempt to convert a given string into its closest ASCII representation: + + use Illuminate\Support\Str; + + $email = Str::of('ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ')->transliterate() + + // 'test@laravel.com' + #### `trim` {.collection-method} @@ -2871,3 +3088,18 @@ The `words` method limits the number of words in a string. If necessary, you may $string = Str::of('Perfectly balanced, as all things should be.')->words(3, ' >>>'); // Perfectly balanced, as >>> + + +#### `wrap` {.collection-method} + +The `wrap` method wraps the given string with an additional string or pair of strings: + + use Illuminate\Support\Str; + + Str::of('Laravel')->wrap('"'); + + // "Laravel" + + Str::is('is')->wrap(before: 'This ', after: ' Laravel!'); + + // This is Laravel! diff --git a/structure.md b/structure.md index 50c181be912..669241f1fdd 100644 --- a/structure.md +++ b/structure.md @@ -39,37 +39,37 @@ The default Laravel application structure is intended to provide a great startin ## The Root Directory -#### The App Directory +### The App Directory The `app` directory contains the core code of your application. We'll explore this directory in more detail soon; however, almost all of the classes in your application will be in this directory. -#### The Bootstrap Directory +### The Bootstrap Directory The `bootstrap` directory contains the `app.php` file which bootstraps the framework. This directory also houses a `cache` directory which contains framework generated files for performance optimization such as the route and services cache files. -#### The Config Directory +### The Config Directory The `config` directory, as the name implies, contains all of your application's configuration files. It's a great idea to read through all of these files and familiarize yourself with all of the options available to you. -#### The Database Directory +### The Database Directory The `database` directory contains your database migrations, model factories, and seeds. If you wish, you may also use this directory to hold an SQLite database. -#### The Public Directory +### The Public Directory The `public` directory contains the `index.php` file, which is the entry point for all requests entering your application and configures autoloading. This directory also houses your assets such as images, JavaScript, and CSS. -#### The Resources Directory +### The Resources Directory The `resources` directory contains your [views](/docs/{{version}}/views) as well as your raw, un-compiled assets such as CSS or JavaScript. -#### The Routes Directory +### The Routes Directory The `routes` directory contains all of the route definitions for your application. By default, two route files are included with Laravel: `web.php` and `console.php`. @@ -84,19 +84,19 @@ The `api.php` file contains routes that are intended to be stateless, so request The `channels.php` file is where you may register all of the [event broadcasting](/docs/{{version}}/broadcasting) channels that your application supports. -#### The Storage Directory +### The Storage Directory The `storage` directory contains your logs, compiled Blade templates, file based sessions, file caches, and other files generated by the framework. This directory is segregated into `app`, `framework`, and `logs` directories. The `app` directory may be used to store any files generated by your application. The `framework` directory is used to store framework generated files and caches. Finally, the `logs` directory contains your application's log files. The `storage/app/public` directory may be used to store user-generated files, such as profile avatars, that should be publicly accessible. You should create a symbolic link at `public/storage` which points to this directory. You may create the link using the `php artisan storage:link` Artisan command. -#### The Tests Directory +### The Tests Directory The `tests` directory contains your automated tests. Example [Pest](https://pestphp.com) or [PHPUnit](https://phpunit.de/) unit tests and feature tests are provided out of the box. Each test class should be suffixed with the word `Test`. You may run your tests using the `/vendor/bin/pest` or `/vendor/bin/phpunit` commands. Or, if you would like a more detailed and beautiful representation of your test results, you may run your tests using the `php artisan test` Artisan command. -#### The Vendor Directory +### The Vendor Directory The `vendor` directory contains your [Composer](https://getcomposer.org) dependencies. @@ -113,68 +113,68 @@ Both the `Console` and `Http` directories are further explained in their respect > Many of the classes in the `app` directory can be generated by Artisan via commands. To review the available commands, run the `php artisan list make` command in your terminal. -#### The Broadcasting Directory +### The Broadcasting Directory The `Broadcasting` directory contains all of the broadcast channel classes for your application. These classes are generated using the `make:channel` command. This directory does not exist by default, but will be created for you when you create your first channel. To learn more about channels, check out the documentation on [event broadcasting](/docs/{{version}}/broadcasting). -#### The Console Directory +### The Console Directory The `Console` directory contains all of the custom Artisan commands for your application. These commands may be generated using the `make:command` command. -#### The Events Directory +### The Events Directory This directory does not exist by default, but will be created for you by the `event:generate` and `make:event` Artisan commands. The `Events` directory houses [event classes](/docs/{{version}}/events). Events may be used to alert other parts of your application that a given action has occurred, providing a great deal of flexibility and decoupling. -#### The Exceptions Directory +### The Exceptions Directory The `Exceptions` directory contains all of the custom exceptions for your application. These exceptions may be generated using the `make:exception` command. -#### The Http Directory +### The Http Directory The `Http` directory contains your controllers, middleware, and form requests. Almost all of the logic to handle requests entering your application will be placed in this directory. -#### The Jobs Directory +### The Jobs Directory This directory does not exist by default, but will be created for you if you execute the `make:job` Artisan command. The `Jobs` directory houses the [queueable jobs](/docs/{{version}}/queues) for your application. Jobs may be queued by your application or run synchronously within the current request lifecycle. Jobs that run synchronously during the current request are sometimes referred to as "commands" since they are an implementation of the [command pattern](https://en.wikipedia.org/wiki/Command_pattern). -#### The Listeners Directory +### The Listeners Directory This directory does not exist by default, but will be created for you if you execute the `event:generate` or `make:listener` Artisan commands. The `Listeners` directory contains the classes that handle your [events](/docs/{{version}}/events). Event listeners receive an event instance and perform logic in response to the event being fired. For example, a `UserRegistered` event might be handled by a `SendWelcomeEmail` listener. -#### The Mail Directory +### The Mail Directory This directory does not exist by default, but will be created for you if you execute the `make:mail` Artisan command. The `Mail` directory contains all of your [classes that represent emails](/docs/{{version}}/mail) sent by your application. Mail objects allow you to encapsulate all of the logic of building an email in a single, simple class that may be sent using the `Mail::send` method. -#### The Models Directory +### The Models Directory The `Models` directory contains all of your [Eloquent model classes](/docs/{{version}}/eloquent). The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database. Each database table has a corresponding "Model" which is used to interact with that table. Models allow you to query for data in your tables, as well as insert new records into the table. -#### The Notifications Directory +### The Notifications Directory This directory does not exist by default, but will be created for you if you execute the `make:notification` Artisan command. The `Notifications` directory contains all of the "transactional" [notifications](/docs/{{version}}/notifications) that are sent by your application, such as simple notifications about events that happen within your application. Laravel's notification feature abstracts sending notifications over a variety of drivers such as email, Slack, SMS, or stored in a database. -#### The Policies Directory +### The Policies Directory This directory does not exist by default, but will be created for you if you execute the `make:policy` Artisan command. The `Policies` directory contains the [authorization policy classes](/docs/{{version}}/authorization) for your application. Policies are used to determine if a user can perform a given action against a resource. -#### The Providers Directory +### The Providers Directory The `Providers` directory contains all of the [service providers](/docs/{{version}}/providers) for your application. Service providers bootstrap your application by binding services in the service container, registering events, or performing any other tasks to prepare your application for incoming requests. In a fresh Laravel application, this directory will already contain the `AppServiceProvider`. You are free to add your own providers to this directory as needed. -#### The Rules Directory +### The Rules Directory This directory does not exist by default, but will be created for you if you execute the `make:rule` Artisan command. The `Rules` directory contains the custom validation rule objects for your application. Rules are used to encapsulate complicated validation logic in a simple object. For more information, check out the [validation documentation](/docs/{{version}}/validation). diff --git a/telescope.md b/telescope.md index 38457bf70d0..6e3e24f3b6d 100644 --- a/telescope.md +++ b/telescope.md @@ -78,7 +78,7 @@ After running `telescope:install`, you should remove the `TelescopeServiceProvid */ public function register(): void { - if ($this->app->environment('local')) { + if ($this->app->environment('local') && class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) { $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class); $this->app->register(TelescopeServiceProvider::class); } diff --git a/testing.md b/testing.md index 15a918a735d..84c7e216418 100644 --- a/testing.md +++ b/testing.md @@ -46,7 +46,7 @@ If you would like to create a test within the `tests/Unit` directory, you may us php artisan make:test UserTest --unit ``` -> [!NOTE] +> [!NOTE] > Test stubs may be customized using [stub publishing](/docs/{{version}}/artisan#stub-customization). Once the test has been generated, you may define test as you normally would using Pest or PHPUnit. To run your tests, execute the `vendor/bin/pest`, `vendor/bin/phpunit`, or `php artisan test` command from your terminal: @@ -78,7 +78,7 @@ class ExampleTest extends TestCase } ``` -> [!WARNING] +> [!WARNING] > If you define your own `setUp` / `tearDown` methods within a test class, be sure to call the respective `parent::setUp()` / `parent::tearDown()` methods on the parent class. Typically, you should invoke `parent::setUp()` at the start of your own `setUp` method, and `parent::tearDown()` at the end of your `tearDown` method. diff --git a/upgrade.md b/upgrade.md index 064f853be10..85d842ce552 100644 --- a/upgrade.md +++ b/upgrade.md @@ -24,6 +24,7 @@ - [Carbon 3](#carbon-3) - [Password Rehashing](#password-rehashing) - [Per-Second Rate Limiting](#per-second-rate-limiting) +- [Spatie Once Package](#spatie-once-package) @@ -35,7 +36,6 @@ - [Doctrine DBAL Removal](#doctrine-dbal-removal) - [Eloquent Model `casts` Method](#eloquent-model-casts-method) - [Spatial Types](#spatial-types) -- [Spatie Once Package](#spatie-once-package) - [The `Enumerable` Contract](#the-enumerable-contract) - [The `UserProvider` Contract](#the-user-provider-contract) - [The `Authenticatable` Contract](#the-authenticatable-contract) @@ -48,7 +48,7 @@ #### Estimated Upgrade Time: 15 Minutes -> [!NOTE] +> [!NOTE] > We attempt to document every possible breaking change. Since some of these breaking changes are in obscure parts of the framework only a portion of these changes may actually affect your application. Want to save time? You can use [Laravel Shift](https://laravelshift.com/) to help automate your application upgrades. @@ -79,8 +79,10 @@ You should update the following dependencies in your application's `composer.jso - `laravel/octane` to `^2.3` (If installed) - `laravel/passport` to `^12.0` (If installed) - `laravel/sanctum` to `^4.0` (If installed) +- `laravel/scout` to `^10.0` (If installed) - `laravel/spark-stripe` to `^5.0` (If installed) - `laravel/telescope` to `^5.0` (If installed) +- `livewire/livewire` to `^3.4` (If installed) - `inertiajs/inertia-laravel` to `^1.0` (If installed) @@ -124,9 +126,15 @@ However, we do **not recommend** that Laravel 10 applications upgrading to Larav #### Password Rehashing -Laravel 11 will automatically rehash your user's passwords during authentication if your hashing algorithm's "work factor" has been updated since the password was last hashed. +**Likelihood Of Impact: Low** + +Laravel 11 will automatically rehash your user's passwords during authentication if your hashing algorithm's "work factor" has been updated since the password was last hashed. + +Typically, this should not disrupt your application; however, if your `User` model's "password" field has a name other than `password`, you should specify the field's name via the model's `authPasswordName` property: -Typically, this should not disrupt your application; however, you may disable this behavior by adding the `rehash_on_login` option to your application's `config/hashing.php` configuration file: + protected $authPasswordName = 'custom_password_field'; + +Alternatively, you may disable password rehashing by adding the `rehash_on_login` option to your application's `config/hashing.php` configuration file: 'rehash_on_login' => false, @@ -162,7 +170,6 @@ public function getAuthPasswordName() The default `User` model included with Laravel receives this method automatically since the method is included within the `Illuminate\Auth\Authenticatable` trait. - #### The `AuthenticationException` Class **Likelihood Of Impact: Very Low** @@ -175,6 +182,20 @@ if ($e instanceof AuthenticationException) { } ``` + +#### Email Verification Notification on Registration + +**Likelihood Of Impact: Very Low** + +The `SendEmailVerificationNotification` listener is now automatically registered for the `Registered` event if it is not already registered by your application's `EventServiceProvider`. If your application's `EventServiceProvider` does not register this listener and you do not want Laravel to automatically register it for you, you should define an empty `configureEmailVerification` method in your application's `EventServiceProvider`: + +```php +protected function configureEmailVerification() +{ + // ... +} +``` + ### Cache @@ -203,11 +224,11 @@ public function dump(...$args); ### Database -#### SQLite 3.35.0+ +#### SQLite 3.26.0+ **Likelihood Of Impact: High** -If your application is utilizing an SQLite database, SQLite 3.35.0 or greater is required. +If your application is utilizing an SQLite database, SQLite 3.26.0 or greater is required. #### Eloquent Model `casts` Method @@ -414,7 +435,7 @@ public function scalar($query, $bindings = [], $useReadPdo = true); **Likelihood Of Impact: Medium** -Laravel 11 supports both Carbon 2 and Carbon 3. Carbon is a date manipulation library utilized extensively by Laravel and packages throughout the ecosystem. If you install Carbon 3, you should review Carbon's [change log](https://github.com/briannesbitt/Carbon/releases/tag/3.0.0). +Laravel 11 supports both Carbon 2 and Carbon 3. Carbon is a date manipulation library utilized extensively by Laravel and packages throughout the ecosystem. If you upgrade to Carbon 3, be aware that `diffIn*` methods now return floating-point numbers and may return negative values to indicate time direction, which is a significant change from Carbon 2. Review Carbon's [change log](https://github.com/briannesbitt/Carbon/releases/tag/3.0.0) and [documentation](https://carbon.nesbot.com/docs/#api-carbon-3) for detailed information on how to handle these and other changes. ### Mail diff --git a/urls.md b/urls.md index 5d454d5e75a..5becbe7d49f 100644 --- a/urls.md +++ b/urls.md @@ -28,6 +28,32 @@ The `url` helper may be used to generate arbitrary URLs for your application. Th // http://example.com/posts/1 +To generate a URL with query string parameters, you may use the `query` method: + + echo url()->query('/posts', ['search' => 'Laravel']); + + // https://example.com/posts?search=Laravel + + echo url()->query('/posts?sort=latest', ['search' => 'Laravel']); + + // http://example.com/posts?sort=latest&search=Laravel + +Providing query string parameters that already exist in the path will overwrite their existing value: + + echo url()->query('/posts?sort=latest', ['sort' => 'oldest']); + + // http://example.com/posts?sort=oldest + +Arrays of values may also be passed as query parameters. These values will be properly keyed and encoded in the generated URL: + + echo $url = url()->query('/posts', ['columns' => ['title', 'body']]); + + // http://example.com/posts?columns%5B0%5D=title&columns%5B1%5D=body + + echo urldecode($url); + + // http://example.com/posts?columns[0]=title&columns[1]=body + ### Accessing the Current URL @@ -151,7 +177,7 @@ When someone visits a signed URL that has expired, they will receive a generic e ->withExceptions(function (Exceptions $exceptions) { $exceptions->render(function (InvalidSignatureException $e) { - return response()->view('error.link-expired', [], 403); + return response()->view('errors.link-expired', status: 403); }); }) @@ -212,20 +238,9 @@ Setting URL default values can interfere with Laravel's handling of implicit mod ```php ->withMiddleware(function (Middleware $middleware) { - $middleware->priority([ - \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, - \Illuminate\Cookie\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, - \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class, - \Illuminate\Routing\Middleware\ThrottleRequests::class, - \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class, - \Illuminate\Session\Middleware\AuthenticateSession::class, - \App\Http\Middleware\SetDefaultLocaleForUrls::class, // [tl! add] - \Illuminate\Routing\Middleware\SubstituteBindings::class, - \Illuminate\Auth\Middleware\Authorize::class, - ]); + $middleware->prependToPriorityList( + before: \Illuminate\Routing\Middleware\SubstituteBindings::class, + prepend: \App\Http\Middleware\SetDefaultLocaleForUrls::class, + ); }) ``` diff --git a/valet.md b/valet.md index 1676404a460..0a324237735 100644 --- a/valet.md +++ b/valet.md @@ -465,19 +465,19 @@ If you would like to define a custom Valet driver for a single application, crea
-Command | Description -------------- | ------------- -`valet list` | Display a list of all Valet commands. -`valet diagnose` | Output diagnostics to aid in debugging Valet. -`valet directory-listing` | Determine directory-listing behavior. Default is "off", which renders a 404 page for directories. -`valet forget` | Run this command from a "parked" directory to remove it from the parked directory list. -`valet log` | View a list of logs which are written by Valet's services. -`valet paths` | View all of your "parked" paths. -`valet restart` | Restart the Valet daemons. -`valet start` | Start the Valet daemons. -`valet stop` | Stop the Valet daemons. -`valet trust` | Add sudoers files for Brew and Valet to allow Valet commands to be run without prompting for your password. -`valet uninstall` | Uninstall Valet: shows instructions for manual uninstall. Pass the `--force` option to aggressively delete all of Valet's resources. +| Command | Description | +| --- | --- | +| `valet list` | Display a list of all Valet commands. | +| `valet diagnose` | Output diagnostics to aid in debugging Valet. | +| `valet directory-listing` | Determine directory-listing behavior. Default is "off", which renders a 404 page for directories. | +| `valet forget` | Run this command from a "parked" directory to remove it from the parked directory list. | +| `valet log` | View a list of logs which are written by Valet's services. | +| `valet paths` | View all of your "parked" paths. | +| `valet restart` | Restart the Valet daemons. | +| `valet start` | Start the Valet daemons. | +| `valet stop` | Stop the Valet daemons. | +| `valet trust` | Add sudoers files for Brew and Valet to allow Valet commands to be run without prompting for your password. | +| `valet uninstall` | Uninstall Valet: shows instructions for manual uninstall. Pass the `--force` option to aggressively delete all of Valet's resources. |
diff --git a/validation.md b/validation.md index 2154256744b..5d101fedb62 100644 --- a/validation.md +++ b/validation.md @@ -220,10 +220,12 @@ You may use the `@error` [Blade](/docs/{{version}}/blade) directive to quickly d - + class="@error('title') is-invalid @enderror" +/> @error('title')
{{ $message }}
@@ -311,7 +313,7 @@ As you might have guessed, the `authorize` method is responsible for determining /** * Get the validation rules that apply to the request. * - * @return array + * @return array|string> */ public function rules(): array { @@ -413,7 +415,7 @@ By adding a `stopOnFirstFailure` property to your request class, you may inform #### Customizing the Redirect Location -As previously discussed, a redirect response will be generated to send the user back to their previous location when form request validation fails. However, you are free to customize this behavior. To do so, define a `$redirect` property on your form request: +When form request validation fails, a redirect response will be generated to send the user back to their previous location. However, you are free to customize this behavior. To do so, define a `$redirect` property on your form request: /** * The URI that users should be redirected to if validation fails. @@ -559,7 +561,7 @@ If you do not want to use the `validate` method on the request, you may create a ]); if ($validator->fails()) { - return redirect('post/create') + return redirect('/post/create') ->withErrors($validator) ->withInput(); } @@ -611,7 +613,7 @@ You may use the `validateWithBag` method to store the error messages in a [named If you have multiple forms on a single page, you may wish to name the `MessageBag` containing the validation errors, allowing you to retrieve the error messages for a specific form. To achieve this, pass a name as the second argument to `withErrors`: - return redirect('register')->withErrors($validator, 'login'); + return redirect('/register')->withErrors($validator, 'login'); You may then access the named `MessageBag` instance from the `$errors` variable: @@ -879,6 +881,7 @@ Below is a list of all available validation rules and their function: [Between](#rule-between) [Boolean](#rule-boolean) [Confirmed](#rule-confirmed) +[Contains](#rule-contains) [Current Password](#rule-current-password) [Date](#rule-date) [Date Equals](#rule-date-equals) @@ -1097,6 +1100,13 @@ The field under validation must be able to be cast as a boolean. Accepted input The field under validation must have a matching field of `{field}_confirmation`. For example, if the field under validation is `password`, a matching `password_confirmation` field must be present in the input. +You may also pass a custom confirmation field name. For example, `confirmed:repeat_username` will expect the field `repeat_username` to match the field under validation. + + +#### contains:_foo_,_bar_,... + +The field under validation must be an array that contains all of the given parameter values. + #### current_password @@ -1344,7 +1354,7 @@ If you would like to customize the query executed by the validation rule, you ma 'email' => [ 'required', Rule::exists('staff')->where(function (Builder $query) { - return $query->where('account_id', 1); + $query->where('account_id', 1); }), ], ]); @@ -1538,25 +1548,25 @@ The field under validation must be a multiple of _value_. The field under validation must not be present in the input data. - - #### missing_if:_anotherfield_,_value_,... + +#### missing_if:_anotherfield_,_value_,... - The field under validation must not be present if the _anotherfield_ field is equal to any _value_. +The field under validation must not be present if the _anotherfield_ field is equal to any _value_. - - #### missing_unless:_anotherfield_,_value_ + +#### missing_unless:_anotherfield_,_value_ The field under validation must not be present unless the _anotherfield_ field is equal to any _value_. - - #### missing_with:_foo_,_bar_,... + +#### missing_with:_foo_,_bar_,... - The field under validation must not be present _only if_ any of the other specified fields are present. +The field under validation must not be present _only if_ any of the other specified fields are present. - - #### missing_with_all:_foo_,_bar_,... + +#### missing_with_all:_foo_,_bar_,... - The field under validation must not be present _only if_ all of the other specified fields are present. +The field under validation must not be present _only if_ all of the other specified fields are present. #### not_in:_foo_,_bar_,... @@ -1897,7 +1907,7 @@ The field under validation must be a valid [Universally Unique Lexicographically #### uuid -The field under validation must be a valid RFC 4122 (version 1, 3, 4, or 5) universally unique identifier (UUID). +The field under validation must be a valid RFC 9562 (version 1, 3, 4, 5, 6, 7, or 8) universally unique identifier (UUID). ## Conditionally Adding Rules @@ -1928,7 +1938,7 @@ Alternatively, you may use the `exclude_unless` rule to not validate a given fie In some situations, you may wish to run validation checks against a field **only** if that field is present in the data being validated. To quickly accomplish this, add the `sometimes` rule to your rule list: - $v = Validator::make($data, [ + $validator = Validator::make($data, [ 'email' => 'sometimes|required|email', ]); diff --git a/verification.md b/verification.md index d8c2cca2c8d..d6c0a752499 100644 --- a/verification.md +++ b/verification.md @@ -108,7 +108,7 @@ Sometimes a user may misplace or accidentally delete the email address verificat ### Protecting Routes -[Route middleware](/docs/{{version}}/middleware) may be used to only allow verified users to access a given route. Laravel includes a `verified` [middleware alias](/docs/{{version}}/middleware#middleware-alias), which is an alias for the `Illuminate\Auth\Middleware\EnsureEmailIsVerified` middleware class. Since this alias is already automatically registered by Laravel, all you need to do is attach the `verified` middleware to a route definition. Typically, this middleware is paired with the `auth` middleware: +[Route middleware](/docs/{{version}}/middleware) may be used to only allow verified users to access a given route. Laravel includes a `verified` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is an alias for the `Illuminate\Auth\Middleware\EnsureEmailIsVerified` middleware class. Since this alias is already automatically registered by Laravel, all you need to do is attach the `verified` middleware to a route definition. Typically, this middleware is paired with the `auth` middleware: Route::get('/profile', function () { // Only verified users may access this route... diff --git a/vite.md b/vite.md index c14bbca2bef..69bf5b6f5c4 100644 --- a/vite.md +++ b/vite.md @@ -18,6 +18,7 @@ - [Processing Static Assets With Vite](#blade-processing-static-assets) - [Refreshing on Save](#blade-refreshing-on-save) - [Aliases](#blade-aliases) +- [Asset Prefetching](#asset-prefetching) - [Custom Base URLs](#custom-base-urls) - [Environment Variables](#environment-variables) - [Disabling Vite in Tests](#disabling-vite-in-tests) @@ -201,7 +202,7 @@ If your file changes are not being reflected in the browser while the developmen With your Vite entry points configured, you may now reference them in a `@vite()` Blade directive that you add to the `` of your application's root template: ```blade - + {{-- ... --}} @@ -212,7 +213,7 @@ With your Vite entry points configured, you may now reference them in a `@vite() If you're importing your CSS via JavaScript, you only need to include the JavaScript entry point: ```blade - + {{-- ... --}} @@ -398,20 +399,22 @@ 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 }) { - return createApp({ render: () => h(App, props) }) + 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](#asset-prefetching). + > [!NOTE] > Laravel's [starter kits](/docs/{{version}}/starter-kits) already include the proper Laravel, Inertia, and Vite configuration. Check out [Laravel Breeze](/docs/{{version}}/starter-kits#breeze-and-inertia) for 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. +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](#configuring-vite) 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. @@ -499,6 +502,7 @@ export default defineConfig({ 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/**` @@ -561,6 +565,75 @@ Once a macro has been defined, it can be invoked within your templates. For exam 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/{{version}}/providers): + +```php + + addEventListener('load', () => setTimeout(() => { + dispatchEvent(new Event('vite:prefetch')) + }, 3000)) + +``` + ## Custom Base URLs @@ -866,7 +939,7 @@ For example, the `vite-imagetools` plugin outputs URLs like the following while ``` -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 behaviour, you will need to manually correct the URLs. You can do this in your `vite.config.js` file by using the `transformOnServe` option. +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 behaviour, 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: