diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e9560ce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+_book/
+node_modules/
\ No newline at end of file
diff --git a/application-testing.md b/application-testing.md
deleted file mode 100644
index 57d06e5..0000000
--- a/application-testing.md
+++ /dev/null
@@ -1,351 +0,0 @@
-# Application Testing
-
-- [Introduction](#introduction)
-- [Interacting With Your Application](#interacting-with-your-application)
- - [Interacting With Links](#interacting-with-links)
- - [Interacting With Forms](#interacting-with-forms)
-- [Testing JSON APIs](#testing-json-apis)
- - [Verifying Exact Match](#verifying-exact-match)
- - [Verifying Structural Match](#verifying-structural-match)
-- [Sessions / Authentication](#sessions-and-authentication)
-- [Disabling Middleware](#disabling-middleware)
-- [Custom HTTP Requests](#custom-http-requests)
-- [PHPUnit Assertions](#phpunit-assertions)
-
-
-## Introduction
-
-Laravel provides a very fluent API for making HTTP requests to your application, examining the output, and even filling out forms. For example, take a look at the test defined below:
-
- visit('/')
- ->see('Laravel 5')
- ->dontSee('Rails');
- }
- }
-
-The `visit` method makes a `GET` request into the application. The `see` method asserts that we should see the given text in the response returned by the application. The `dontSee` method asserts that the given text is not returned in the application response. This is the most basic application test available in Laravel.
-
-You may also use the 'visitRoute' method to make a 'GET' request via a named route:
-
- $this->visitRoute('profile');
-
- $this->visitRoute('profile', ['user' => 1]);
-
-
-## Interacting With Your Application
-
-Of course, you can do much more than simply assert that text appears in a given response. Let's take a look at some examples of clicking links and filling out forms:
-
-
-### Interacting With Links
-
-In this test, we will make a request to the application, "click" a link in the returned response, and then assert that we landed on a given URI. For example, let's assume there is a link in our response that has a text value of "About Us":
-
- About Us
-
-Now, let's write a test that clicks the link and asserts the user lands on the correct page:
-
- public function testBasicExample()
- {
- $this->visit('/')
- ->click('About Us')
- ->seePageIs('/about-us');
- }
-
-You may also check that the user has arrived at the correct named route using the `seeRouteIs` method:
-
- ->seeRouteIs('profile', ['user' => 1]);
-
-
-### Interacting With Forms
-
-Laravel also provides several methods for testing forms. The `type`, `select`, `check`, `attach`, and `press` methods allow you to interact with all of your form's inputs. For example, let's imagine this form exists on the application's registration page:
-
-
-
-We can write a test to complete this form and inspect the result:
-
- public function testNewUserRegistration()
- {
- $this->visit('/register')
- ->type('Taylor', 'name')
- ->check('terms')
- ->press('Register')
- ->seePageIs('/dashboard');
- }
-
-Of course, if your form contains other inputs such as radio buttons or drop-down boxes, you may easily fill out those types of fields as well. Here is a list of each form manipulation method:
-
-Method | Description
-------------- | -------------
-`$this->type($text, $elementName)` | "Type" text into a given field.
-`$this->select($value, $elementName)` | "Select" a radio button or drop-down field.
-`$this->check($elementName)` | "Check" a checkbox field.
-`$this->uncheck($elementName)` | "Uncheck" a checkbox field.
-`$this->attach($pathToFile, $elementName)` | "Attach" a file to the form.
-`$this->press($buttonTextOrElementName)` | "Press" a button with the given text or name.
-
-
-#### File Inputs
-
-If your form contains `file` inputs, you may attach files to the form using the `attach` method:
-
- public function testPhotoCanBeUploaded()
- {
- $this->visit('/upload')
- ->attach($pathToFile, 'photo')
- ->press('Upload')
- ->see('Upload Successful!');
- }
-
-
-### Testing JSON APIs
-
-Laravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `get`, `post`, `put`, `patch`, and `delete` methods may be used to issue requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/user` and assert that the expected data was returned:
-
- json('POST', '/user', ['name' => 'Sally'])
- ->seeJson([
- 'created' => true,
- ]);
- }
- }
-
-> {tip} The `seeJson` method converts the given array into JSON, and then verifies that the JSON fragment occurs **anywhere** within the entire 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.
-
-
-### Verifying Exact Match
-
-If you would like to verify that the given array is an **exact** match for the JSON returned by the application, you should use the `seeJsonEquals` method:
-
- json('POST', '/user', ['name' => 'Sally'])
- ->seeJsonEquals([
- 'created' => true,
- ]);
- }
- }
-
-
-### Verifying Structural Match
-
-It is also possible to verify that a JSON response adheres to a specific structure. In this scenario, you should use the `seeJsonStructure` method and pass it your expected JSON structure:
-
- get('/user/1')
- ->seeJsonStructure([
- 'name',
- 'pet' => [
- 'name', 'age'
- ]
- ]);
- }
- }
-
-The above example illustrates an expectation of receiving a `name` attribute and a nested `pet` object with its own `name` and `age` attributes. `seeJsonStructure` will not fail if additional keys are present in the response. For example, the test would still pass if the `pet` had a `weight` attribute.
-
-You may use the `*` to assert that the returned JSON structure has a list where each list item contains at least the attributes found in the set of values:
-
- get('/users')
- ->seeJsonStructure([
- '*' => [
- 'id', 'name', 'email'
- ]
- ]);
- }
- }
-
-You may also nest the `*` notation. In this case, we will assert that each user in the JSON response contains a given set of attributes and that each pet on each user also contains a given set of attributes:
-
- $this->get('/users')
- ->seeJsonStructure([
- '*' => [
- 'id', 'name', 'email', 'pets' => [
- '*' => [
- 'name', 'age'
- ]
- ]
- ]
- ]);
-
-
-### Sessions / Authentication
-
-Laravel provides several helpers for working with the session during testing. First, you may set the session data to a given array using the `withSession` method. This is useful for loading the session with data before issuing a request to your application:
-
- withSession(['foo' => 'bar'])
- ->visit('/');
- }
- }
-
-Of course, one common use of the session is for maintaining state for the authenticated user. The `actingAs` helper method provides a simple way to authenticate a given user as the current user. For example, we may use a [model factory](#model-factories) to generate and authenticate a user:
-
- create();
-
- $this->actingAs($user)
- ->withSession(['foo' => 'bar'])
- ->visit('/')
- ->see('Hello, '.$user->name);
- }
- }
-
-You may also specify which guard should be used to authenticate the given user by passing the guard name as the second argument to the `actingAs` method:
-
- $this->actingAs($user, 'api')
-
-
-### Disabling Middleware
-
-When testing your application, you may find it convenient to disable [middleware](/docs/{{version}}/middleware) for some of your tests. This will allow you to test your routes and controller in isolation from any middleware concerns. Laravel includes a simple `WithoutMiddleware` trait that you can use to automatically disable all middleware for the test class:
-
- withoutMiddleware();
-
- $this->visit('/')
- ->see('Laravel 5');
- }
- }
-
-
-### Custom HTTP Requests
-
-If you would like to make a custom HTTP request into your application and get the full `Illuminate\Http\Response` object, you may use the `call` method:
-
- public function testApplication()
- {
- $response = $this->call('GET', '/');
-
- $this->assertEquals(200, $response->status());
- }
-
-If you are making `POST`, `PUT`, or `PATCH` requests you may pass an array of input data with the request. Of course, this data will be available in your routes and controller via the [Request instance](/docs/{{version}}/requests):
-
- $response = $this->call('POST', '/user', ['name' => 'Taylor']);
-
-
-### PHPUnit Assertions
-
-Laravel provides a variety of custom assertion methods for [PHPUnit](https://phpunit.de/) tests:
-
-Method | Description
-------------- | -------------
-`->assertResponseOk();` | Assert that the client response has an OK status code.
-`->assertResponseStatus($code);` | Assert that the client response has a given code.
-`->assertViewHas($key, $value = null);` | Assert that the response view has a given piece of bound data.
-`->assertViewHasAll(array $bindings);` | Assert that the view has a given list of bound data.
-`->assertViewMissing($key);` | Assert that the response view is missing a piece of bound data.
-`->assertRedirectedTo($uri, $with = []);` | Assert whether the client was redirected to a given URI.
-`->assertRedirectedToRoute($name, $parameters = [], $with = []);` | Assert whether the client was redirected to a given route.
-`->assertRedirectedToAction($name, $parameters = [], $with = []);` | Assert whether the client was redirected to a given action.
-`->assertSessionHas($key, $value = null);` | Assert that the session has a given value.
-`->assertSessionHasAll(array $bindings);` | Assert that the session has a given list of values.
-`->assertSessionHasErrors($bindings = [], $format = null);` | Assert that the session has errors bound.
-`->assertHasOldInput();` | Assert that the session has old input.
-`->assertSessionMissing($key);` | Assert that the session is missing a given key.
diff --git a/artisan.md b/artisan.md
index a910c6f..1afe87d 100644
--- a/artisan.md
+++ b/artisan.md
@@ -1,9 +1,10 @@
-# Console Commands
+# Artisan Console
- [Introduction](#introduction)
- [Writing Commands](#writing-commands)
- [Generating Commands](#generating-commands)
- [Command Structure](#command-structure)
+ - [Closure Commands](#closure-commands)
- [Defining Input Expectations](#defining-input-expectations)
- [Arguments](#arguments)
- [Options](#options)
@@ -14,7 +15,7 @@
- [Prompting For Input](#prompting-for-input)
- [Writing Output](#writing-output)
- [Registering Commands](#registering-commands)
-- [Programatically Executing Commands](#programatically-executing-commands)
+- [Programmatically Executing Commands](#programmatically-executing-commands)
- [Calling Commands From Other Commands](#calling-commands-from-other-commands)
@@ -28,6 +29,12 @@ Every command also includes a "help" screen which displays and describes the com
php artisan help migrate
+#### Laravel REPL
+
+All Laravel applications include Tinker, a REPL powered by the [PsySH](https://github.com/bobthecow/psysh) package. Tinker allows you to interact with your entire Laravel application on the command line, including the Eloquent ORM, jobs, events, and more. To enter the Tinker environment, run the `tinker` Artisan command:
+
+ php artisan tinker
+
## Writing Commands
@@ -36,10 +43,12 @@ In addition to the commands provided with Artisan, you may also build your own c
### Generating Commands
-To create a new command, use the `make:command` Artisan command. This command will create a new command class in the `app/Console/Commands` directory. The generated command will include the default set of properties and methods that are present on all commands:
+To create a new command, use the `make:command` Artisan command. This command will create a new command class in the `app/Console/Commands` directory. Don't worry if this directory does not exist in your application, since it will be created the first time you run the `make:command` Artisan command. The generated command will include the default set of properties and methods that are present on all commands:
php artisan make:command SendEmails
+Next, you will need to [register the command](#registering-commands) before it can be executed via the Artisan CLI.
+
### Command Structure
@@ -104,6 +113,48 @@ Let's take a look at an example command. Note that we are able to inject any dep
}
}
+
+### Closure Commands
+
+Closure based commands provide an alternative to defining console commands as classes. In the same way that route Closures are an alternative to controllers, think of command Closures as an alternative to command classes. Within the `commands` method of your `app/Console/Kernel.php` file, Laravel loads the `routes/console.php` file:
+
+ /**
+ * Register the Closure based commands for the application.
+ *
+ * @return void
+ */
+ protected function commands()
+ {
+ require base_path('routes/console.php');
+ }
+
+Even though this file does not define HTTP routes, it defines console based entry points (routes) into your application. Within this file, you may define all of your Closure based routes using the `Artisan::command` method. The `command` method accepts two arguments: the [command signature](#defining-input-expectations) and a Closure which receives the commands arguments and options:
+
+ Artisan::command('build {project}', function ($project) {
+ $this->info("Building {$project}!");
+ });
+
+The Closure is bound to the underlying command instance, so you have full access to all of the helper methods you would typically be able to access on a full command class.
+
+#### Type-Hinting Dependencies
+
+In addition to receiving your command's arguments and options, command Closures may also type-hint additional dependencies that you would like resolved out of the [service container](/docs/{{version}}/container):
+
+ use App\User;
+ use App\DripEmailer;
+
+ Artisan::command('email:send {user}', function (DripEmailer $drip, $user) {
+ $drip->send(User::find($user));
+ });
+
+#### Closure Command Descriptions
+
+When defining a Closure based command, you may use the `describe` method to add a description to the command. This description will be displayed when you run the `php artisan list` or `php artisan help` commands:
+
+ Artisan::command('build {project}', function ($project) {
+ $this->info("Building {$project}!");
+ })->describe('Build the project');
+
## Defining Input Expectations
@@ -258,9 +309,9 @@ The `secret` method is similar to `ask`, but the user's input will not be visibl
#### Asking For Confirmation
-If you need to ask the user for a simple confirmation, you may use the `confirm` method. By default, this method will return `false`. However, if the user enters `y` in response to the prompt, the method will return `true`.
+If you need to ask the user for a simple confirmation, you may use the `confirm` method. By default, this method will return `false`. However, if the user enters `y` or `yes` in response to the prompt, the method will return `true`.
- if ($this->confirm('Do you wish to continue? [y|N]')) {
+ if ($this->confirm('Do you wish to continue?')) {
//
}
@@ -325,7 +376,7 @@ For long running tasks, it could be helpful to show a progress indicator. Using
$bar->finish();
-For more advanced options, check out the [Symfony Progress Bar component documentation](http://symfony.com/doc/2.7/components/console/helpers/progressbar.html).
+For more advanced options, check out the [Symfony Progress Bar component documentation](https://symfony.com/doc/2.7/components/console/helpers/progressbar.html).
## Registering Commands
@@ -336,8 +387,8 @@ Once your command is finished, you need to register it with Artisan. All command
Commands\SendEmails::class
];
-
-## Programatically Executing Commands
+
+## Programmatically Executing Commands
Sometimes you may wish to execute an Artisan command outside of the CLI. For example, you may wish to fire an Artisan command from a route or controller. You may use the `call` method on the `Artisan` facade to accomplish this. The `call` method accepts the name of the command as the first argument, and an array of command parameters as the second argument. The exit code will be returned:
diff --git a/authentication.md b/authentication.md
index 669621b..99edacc 100644
--- a/authentication.md
+++ b/authentication.md
@@ -14,6 +14,7 @@
- [Other Authentication Methods](#other-authentication-methods)
- [HTTP Basic Authentication](#http-basic-authentication)
- [Stateless HTTP Basic Authentication](#stateless-http-basic-authentication)
+- [Social Authentication](https://github.com/laravel/socialite)
- [Adding Custom Guards](#adding-custom-guards)
- [Adding Custom User Providers](#adding-custom-user-providers)
- [The User Provider Contract](#the-user-provider-contract)
@@ -23,7 +24,7 @@
## Introduction
-> {tip} **Want to get started fast?** Just run `php artisan make:auth` in a fresh Laravel application and navigate your browser to `http://your-app.dev/register` or any other URL that is assigned to your application. This single command will take care of scaffolding your entire authentication system!
+> {tip} **Want to get started fast?** Just run `php artisan make:auth` and `php artisan migrate` in a fresh Laravel application. Then, navigate your browser to `http://your-app.dev/register` or any other URL that is assigned to your application. These two commands will take care of scaffolding your entire authentication system!
Laravel makes implementing authentication very simple. In fact, almost everything is configured for you out of the box. The authentication configuration file is located at `config/auth.php`, which contains several well documented options for tweaking the behavior of the authentication services.
@@ -74,7 +75,23 @@ When a user is successfully authenticated, they will be redirected to the `/home
protected $redirectTo = '/';
-When a user is not successfully authenticated, they will be automatically redirected back to the login form.
+If the redirect path needs custom generation logic you may define a `redirectTo` method instead of a `redirectTo` property:
+
+ protected function redirectTo()
+ {
+ //
+ }
+
+> {tip} The `redirectTo` method will take precedence over the `redirectTo` attribute.
+
+#### Username Customization
+
+By default, Laravel uses the `email` field for authentication. If you would like to customize this, you may define a `username` method on your `LoginController`:
+
+ public function username()
+ {
+ return 'username';
+ }
#### Guard Customization
@@ -102,8 +119,12 @@ You may access the authenticated user via the `Auth` facade:
use Illuminate\Support\Facades\Auth;
+ // Get the currently authenticated user...
$user = Auth::user();
+ // Get the currently authenticated user's ID...
+ $id = Auth::id();
+
Alternatively, once a user is authenticated, you may access the authenticated user via an `Illuminate\Http\Request` instance. Remember, type-hinted classes will automatically be injected into your controller methods:
middleware('auth');
@@ -181,7 +202,7 @@ We will access Laravel's authentication services via the `Auth` [facade](/docs/{
use Illuminate\Support\Facades\Auth;
- class AuthController extends Controller
+ class LoginController extends Controller
{
/**
* Handle an authentication attempt.
@@ -273,7 +294,7 @@ To log a user into the application by their ID, you may use the `loginUsingId` m
#### Authenticate A User Once
-You may use the `once` method to log a user into the application for a single request. No sessions or cookies will be utilized, which means this method may be helpful when building a stateless API. The `once` method has the same signature as the `attempt` method:
+You may use the `once` method to log a user into the application for a single request. No sessions or cookies will be utilized, which means this method may be helpful when building a stateless API:
if (Auth::once($credentials)) {
//
@@ -282,9 +303,9 @@ You may use the `once` method to log a user into the application for a single re
## HTTP Basic Authentication
-[HTTP Basic Authentication](http://en.wikipedia.org/wiki/Basic_access_authentication) provides a quick way to authenticate users of your application without setting up a dedicated "login" page. To get started, attach the `auth.basic` [middleware](/docs/{{version}}/middleware) to your route. The `auth.basic` middleware is included with the Laravel framework, so you do not need to define it:
+[HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) provides a quick way to authenticate users of your application without setting up a dedicated "login" page. To get started, attach the `auth.basic` [middleware](/docs/{{version}}/middleware) to your route. The `auth.basic` middleware is included with the Laravel framework, so you do not need to define it:
- Route::get('profile', function() {
+ Route::get('profile', function () {
// Only authenticated users may enter...
})->middleware('auth.basic');
@@ -326,7 +347,7 @@ You may also use HTTP Basic Authentication without setting a user identifier coo
Next, [register the route middleware](/docs/{{version}}/middleware#registering-middleware) and attach it to a route:
- Route::get('api/user', function() {
+ Route::get('api/user', function () {
// Only authenticated users may enter...
})->middleware('auth.basic.once');
@@ -341,7 +362,7 @@ You may define your own authentication guards using the `extend` method on the `
use App\Services\Auth\JwtGuard;
use Illuminate\Support\Facades\Auth;
- use Illuminate\Support\ServiceProvider;
+ use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
@@ -354,7 +375,7 @@ You may define your own authentication guards using the `extend` method on the `
{
$this->registerPolicies();
- Auth::extend('jwt', function($app, $name, array $config) {
+ Auth::extend('jwt', function ($app, $name, array $config) {
// Return an instance of Illuminate\Contracts\Auth\Guard...
return new JwtGuard(Auth::createUserProvider($config['provider']));
@@ -395,7 +416,7 @@ If you are not using a traditional relational database to store your users, you
{
$this->registerPolicies();
- Auth::provider('riak', function($app, array $config) {
+ Auth::provider('riak', function ($app, array $config) {
// Return an instance of Illuminate\Contracts\Auth\UserProvider...
return new RiakUserProvider($app->make('riak.connection'));
@@ -484,14 +505,26 @@ Laravel raises a variety of [events](/docs/{{version}}/events) during the authen
* @var array
*/
protected $listen = [
+ 'Illuminate\Auth\Events\Registered' => [
+ 'App\Listeners\LogRegisteredUser',
+ ],
+
'Illuminate\Auth\Events\Attempting' => [
'App\Listeners\LogAuthenticationAttempt',
],
+ 'Illuminate\Auth\Events\Authenticated' => [
+ 'App\Listeners\LogAuthenticated',
+ ],
+
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
+ 'Illuminate\Auth\Events\Failed' => [
+ 'App\Listeners\LogFailedLogin',
+ ],
+
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],
diff --git a/authorization.md b/authorization.md
index ca0ee91..8974037 100644
--- a/authorization.md
+++ b/authorization.md
@@ -24,11 +24,12 @@ In addition to providing [authentication](/docs/{{version}}/authentication) serv
Think of gates and policies like routes and controllers. Gates provide a simple, Closure based approach to authorization while policies, like controllers, group their logic around a particular model or resource. We'll explore gates first and then examine policies.
-It is important to not view gates and policies as mutually exclusive for your application. Most applications will most likely contain a mixture of gates and policies, and that is perfectly fine! Gates are most applicable to actions which are not related to any model or resource, such as viewing an administrator dashboard. In contrast, policies should be used when you wish to authorize an action for a particular model or resource.
+You do not need to choose between exclusively using gates or exclusively using policies when building an application. Most applications will most likely contain a mixture of gates and policies, and that is perfectly fine! Gates are most applicable to actions which are not related to any model or resource, such as viewing an administrator dashboard. In contrast, policies should be used when you wish to authorize an action for a particular model or resource.
## Gates
+
### Writing Gates
Gates are Closures that determine if a user is authorized to perform a given action and are typically defined in the `App\Providers\AuthServiceProvider` class using the `Gate` facade. Gates always receive a user instance as their first argument, and may optionally receive additional arguments such as a relevant Eloquent model:
@@ -50,11 +51,15 @@ Gates are Closures that determine if a user is authorized to perform a given act
### Authorizing Actions
-To authorize an action using gates, you should use the `allows` method. Note that you are not required to pass the currently authenticated user to the `allows` method. Laravel will automatically take care of passing the user into the gate Closure:
+To authorize an action using gates, you should use the `allows` or `denies` methods. Note that you are not required to pass the currently authenticated user to these methods. Laravel will automatically take care of passing the user into the gate Closure:
if (Gate::allows('update-post', $post)) {
// The current user can update the post...
- });
+ }
+
+ if (Gate::denies('update-post', $post)) {
+ // The current user can't update the post...
+ }
If you would like to determine if a particular user is authorized to perform an action, you may use the `forUser` method on the `Gate` facade:
@@ -62,6 +67,10 @@ If you would like to determine if a particular user is authorized to perform an
// The user can update the post...
}
+ if (Gate::forUser($user)->denies('update-post', $post)) {
+ // The user can't update the post...
+ }
+
## Creating Policies
@@ -172,8 +181,6 @@ When defining policy methods that will not receive a model instance, such as a `
//
}
-> {tip} If you used the `--model` option when generating your policy, all of the relevant "CRUD" policy methods will already be defined on the generated policy.
-
### Policy Filters
@@ -186,6 +193,8 @@ For certain users, you may wish to authorize all actions within a given policy.
}
}
+If you would like to deny all authorizations for a user you should return `false` from the `before` method. If `null` is returned, the authorization will fall through to the policy method.
+
## Authorizing Actions Using Policies
@@ -281,17 +290,21 @@ As previously discussed, some actions like `create` may not require a model inst
### Via Blade Templates
-When writing Blade templates, you may wish to display a portion of the page only if the user is authorized to perform a given action. For example, you may wish to show an update form for a blog post only if the user can actually update the post. In this situation, you may use the `@can` and `@cannot` directives.
+When writing Blade templates, you may wish to display a portion of the page only if the user is authorized to perform a given action. For example, you may wish to show an update form for a blog post only if the user can actually update the post. In this situation, you may use the `@can` and `@cannot` family of directives:
@can('update', $post)
+ @elsecan('create', $post)
+
@endcan
@cannot('update', $post)
+ @elsecannot('create', $post)
+
@endcannot
-These directives are convenient short-cuts for writing `@if` and `@unless` statements. The `@can` and `@cannot` statements above respectively translate to the following statements:
+These directives are convenient shortcuts for writing `@if` and `@unless` statements. The `@can` and `@cannot` statements above respectively translate to the following statements:
@if (Auth::user()->can('update', $post))
diff --git a/billing.md b/billing.md
index 5388cd0..83f7e5d 100644
--- a/billing.md
+++ b/billing.md
@@ -30,7 +30,7 @@
## Introduction
-Laravel Cashier provides an expressive, fluent interface to [Stripe's](https://stripe.com) and [Braintree's](https://braintreepayments.com) subscription billing services. It handles almost all of the boilerplate subscription billing code you are dreading writing. In addition to basic subscription management, Cashier can handle coupons, swapping subscription, subscription "quantities", cancellation grace periods, and even generate invoice PDFs.
+Laravel Cashier provides an expressive, fluent interface to [Stripe's](https://stripe.com) and [Braintree's](https://www.braintreepayments.com) subscription billing services. It handles almost all of the boilerplate subscription billing code you are dreading writing. In addition to basic subscription management, Cashier can handle coupons, swapping subscription, subscription "quantities", cancellation grace periods, and even generate invoice PDFs.
> {note} If you're only performing "one-off" charges and do not offer subscriptions. You should not use Cashier. You should use the Stripe and Braintree SDKs directly.
@@ -44,7 +44,7 @@ Laravel Cashier provides an expressive, fluent interface to [Stripe's](https://s
First, add the Cashier package for Stripe to your `composer.json` file and run the `composer update` command:
- "laravel/cashier": "~6.0"
+ "laravel/cashier": "~7.0"
#### Service Provider
@@ -112,7 +112,7 @@ For many operations, the Stripe and Braintree implementations of Cashier functio
First, add the Cashier package for Braintree to your `composer.json` file and run the `composer update` command:
- "laravel/cashier-braintree": "~1.0"
+ "laravel/cashier-braintree": "~2.0"
#### Service Provider
@@ -175,10 +175,10 @@ Next, You should configure the following options in your `services.php` file:
Then you should add the following Braintree SDK calls to your `AppServiceProvider` service provider's `boot` method:
- \Braintree_Configuration::environment(env('BRAINTREE_ENV'));
- \Braintree_Configuration::merchantId(env('BRAINTREE_MERCHANT_ID'));
- \Braintree_Configuration::publicKey(env('BRAINTREE_PUBLIC_KEY'));
- \Braintree_Configuration::privateKey(env('BRAINTREE_PRIVATE_KEY'));
+ \Braintree_Configuration::environment(config('services.braintree.environment'));
+ \Braintree_Configuration::merchantId(config('services.braintree.merchant_id'));
+ \Braintree_Configuration::publicKey(config('services.braintree.public_key'));
+ \Braintree_Configuration::privateKey(config('services.braintree.private_key'));
### Currency Configuration
@@ -199,7 +199,7 @@ To create a subscription, first retrieve an instance of your billable model, whi
$user = User::find(1);
- $user->newSubscription('main', 'monthly')->create($creditCardToken);
+ $user->newSubscription('main', 'monthly')->create($stripeToken);
The first argument passed to the `newSubscription` method should be the name of the subscription. If your application only offers a single subscription, you might call this `main` or `primary`. The second argument is the specific Stripe / Braintree plan the user is subscribing to. This value should correspond to the plan's identifier in Stripe or Braintree.
@@ -209,7 +209,7 @@ The `create` method will begin the subscription as well as update your database
If you would like to specify additional customer details, you may do so by passing them as the second argument to the `create` method:
- $user->newSubscription('main', 'monthly')->create($creditCardToken, [
+ $user->newSubscription('main', 'monthly')->create($stripeToken, [
'email' => $email,
]);
@@ -221,7 +221,7 @@ If you would like to apply a coupon when creating the subscription, you may use
$user->newSubscription('main', 'monthly')
->withCoupon('code')
- ->create($creditCardToken);
+ ->create($stripeToken);
### Checking Subscription Status
@@ -358,7 +358,7 @@ If the user cancels a subscription and then resumes that subscription before the
The `updateCard` method may be used to update a customer's credit card information. This method accepts a Stripe token and will assign the new credit card as the default billing source:
- $user->updateCard($creditCardToken);
+ $user->updateCard($stripeToken);
## Subscription Trials
@@ -372,7 +372,7 @@ If you would like to offer trial periods to your customers while still collectin
$user->newSubscription('main', 'monthly')
->trialDays(10)
- ->create($creditCardToken);
+ ->create($stripeToken);
This method will set the trial period ending date on the subscription record within the database, as well as instruct Stripe / Braintree to not begin billing the customer until after this date.
@@ -398,6 +398,8 @@ If you would like to offer trial periods without collecting the user's payment m
'trial_ends_at' => Carbon::now()->addDays(10),
]);
+> {note} Be sure to add a [date mutator](/docs/{{version}}/eloquent-mutators#date-mutators) for `trial_ends_at` to your model definition.
+
Cashier refers to this type of trial as a "generic trial", since it is not attached to any existing subscription. The `onTrial` method on the `User` instance will return `true` if the current date is not past the value of `trial_ends_at`:
if ($user->onTrial()) {
@@ -414,7 +416,7 @@ Once you are ready to create an actual subscription for the user, you may use th
$user = User::find(1);
- $user->newSubscription('main', 'monthly')->create($creditCardToken);
+ $user->newSubscription('main', 'monthly')->create($stripeToken);
## Handling Stripe Webhooks
@@ -432,7 +434,7 @@ By default, this controller will automatically handle cancelling subscriptions t
#### Webhooks & CSRF Protection
-Since Stripe webhooks need to bypass Laravel's [CSRF protection](/docs/{{version}}/routing#csrf-protection), be sure to list the URI as an exception in your `VerifyCsrfToken` middleware or list the route outside of the `web` middleware group:
+Since Stripe webhooks need to bypass Laravel's [CSRF protection](/docs/{{version}}/csrf), be sure to list the URI as an exception in your `VerifyCsrfToken` middleware or list the route outside of the `web` middleware group:
protected $except = [
'stripe/*',
@@ -466,7 +468,7 @@ Cashier automatically handles subscription cancellation on failed charges, but i
### Failed Subscriptions
-What if a customer's credit card expires? No worries - the Cashier webhook controller that can easily cancel the customer's subscription for you. As noted above, all you need to do is point a route to the controller:
+What if a customer's credit card expires? No worries - Cashier includes a Webhook controller that can easily cancel the customer's subscription for you. As noted above, all you need to do is point a route to the controller:
Route::post(
'stripe/webhook',
@@ -481,7 +483,7 @@ That's it! Failed payments will be captured and handled by the controller. The c
Both Stripe and Braintree can notify your application of a variety of events via webhooks. To handle Braintree webhooks, define a route that points to Cashier's webhook controller. This controller will handle all incoming webhook requests and dispatch them to the proper controller method:
Route::post(
- 'stripe/webhook',
+ 'braintree/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
@@ -491,10 +493,10 @@ By default, this controller will automatically handle cancelling subscriptions t
#### Webhooks & CSRF Protection
-Since Braintree webhooks need to bypass Laravel's [CSRF protection](/docs/{{version}}/routing#csrf-protection), be sure to list the URI as an exception in your `VerifyCsrfToken` middleware or list the route outside of the `web` middleware group:
+Since Braintree webhooks need to bypass Laravel's [CSRF protection](/docs/{{version}}/csrf), be sure to list the URI as an exception in your `VerifyCsrfToken` middleware or list the route outside of the `web` middleware group:
protected $except = [
- 'stripe/*',
+ 'braintree/*',
];
diff --git a/blade.md b/blade.md
index 0ae3537..ebd3e10 100644
--- a/blade.md
+++ b/blade.md
@@ -4,6 +4,7 @@
- [Template Inheritance](#template-inheritance)
- [Defining A Layout](#defining-a-layout)
- [Extending A Layout](#extending-a-layout)
+- [Components & Slots](#components-and-slots)
- [Displaying Data](#displaying-data)
- [Blade & JavaScript Frameworks](#blade-and-javascript-frameworks)
- [Control Structures](#control-structures)
@@ -11,6 +12,7 @@
- [Loops](#loops)
- [The Loop Variable](#the-loop-variable)
- [Comments](#comments)
+ - [PHP](#php)
- [Including Sub-Views](#including-sub-views)
- [Rendering Views For Collections](#rendering-views-for-collections)
- [Stacks](#stacks)
@@ -80,6 +82,51 @@ Blade views may be returned from routes using the global `view` helper:
return view('child');
});
+
+## Components & Slots
+
+Components and slots provide similar benefits to sections and layouts; however, some may find the mental model of components and slots easier to understand. First, let's imagine a reusable "alert" component we would like to reuse throughout our application:
+
+
+
+
+ {{ $slot }}
+
+
+The `{{ $slot }}` variable will contain the content we wish to inject into the component. Now, to construct this component, we can use the `@component` Blade directive:
+
+ @component('alert')
+ Whoops! Something went wrong!
+ @endcomponent
+
+Sometimes it is helpful to define multiple slots for a component. Let's modify our alert component to allow for the injection of a "title". Named slots may be displayed by simply "echoing" the variable that matches their name:
+
+
+
+
+
{{ $title }}
+
+ {{ $slot }}
+
+
+Now, we can inject content into the named slot using the `@slot` directive. Any content not within a `@slot` directive will be passed to the component in the `$slot` variable:
+
+ @component('alert')
+ @slot('title')
+ Forbidden
+ @endslot
+
+ You are not allowed to access this resource!
+ @endcomponent
+
+#### Passing Additional Data To Components
+
+Sometimes you may need to pass additional data to a component. For this reason, you can pass an array of data as the second argument to the `@component` directive. All of the data will be made available to the component template as variables:
+
+ @component('alert', ['foo' => 'bar'])
+ ...
+ @endcomponent
+
## Displaying Data
@@ -105,7 +152,7 @@ Sometimes you may wish to echo a variable, but you aren't sure if the variable h
{{ isset($name) ? $name : 'Default' }}
-However, instead of writing a ternary statement, Blade provides you with the following convenient short-cut, which will be compiled to the ternary statement above:
+However, instead of writing a ternary statement, Blade provides you with the following convenient shortcut, which will be compiled to the ternary statement above:
{{ $name or 'Default' }}
@@ -143,7 +190,7 @@ If you are displaying JavaScript variables in a large portion of your template,
## Control Structures
-In addition to template inheritance and displaying data, Blade also provides convenient short-cuts for common PHP control structures, such as conditional statements and loops. These short-cuts provide a very clean, terse way of working with PHP control structures, while also remaining familiar to their PHP counterparts.
+In addition to template inheritance and displaying data, Blade also provides convenient shortcuts for common PHP control structures, such as conditional statements and loops. These shortcuts provide a very clean, terse way of working with PHP control structures, while also remaining familiar to their PHP counterparts.
### If Statements
@@ -260,6 +307,17 @@ Blade also allows you to define comments in your views. However, unlike HTML com
{{-- This comment will not be present in the rendered HTML --}}
+
+### PHP
+
+In some situations, it's useful to embed PHP code into your views. You can use the Blade `@php` directive to execute a block of plain PHP within your template:
+
+ @php
+ //
+ @endphp
+
+> {tip} While Blade provides this feature, using it frequently may be a signal that you have too much logic embedded within your template.
+
## Including Sub-Views
@@ -277,6 +335,10 @@ Even though the included view will inherit all data available in the parent view
@include('view.name', ['some' => 'data'])
+Of course, if you attempt to `@include` a view which does not exist, Laravel will throw an error. If you would like to include a view that may or may not be present, you should use the `@includeIf` directive:
+
+ @includeIf('view.name', ['some' => 'data'])
+
> {note} You should avoid using the `__DIR__` and `__FILE__` constants in your Blade views, since they will refer to the location of the cached, compiled view.
@@ -343,8 +405,8 @@ The following example creates a `@datetime($var)` directive which formats a give
*/
public function boot()
{
- Blade::directive('datetime', function($expression) {
- return "format('m/d/Y H:i'); ?>";
+ Blade::directive('datetime', function ($expression) {
+ return "format('m/d/Y H:i'); ?>";
});
}
@@ -361,6 +423,6 @@ The following example creates a `@datetime($var)` directive which formats a give
As you can see, we will chain the `format` method onto whatever expression is passed into the directive. So, in this example, the final PHP generated by this directive will be:
- format('m/d/Y H:i'); ?>
+ format('m/d/Y H:i'); ?>
> {note} After updating the logic of a Blade directive, you will need to delete all of the cached Blade views. The cached Blade views may be removed using the `view:clear` Artisan command.
diff --git a/broadcasting.md b/broadcasting.md
index 01d92ab..596fff8 100644
--- a/broadcasting.md
+++ b/broadcasting.md
@@ -4,8 +4,9 @@
- [Configuration](#configuration)
- [Driver Prerequisites](#driver-prerequisites)
- [Concept Overview](#concept-overview)
- - [Using Example Application](#using-example-application)
+ - [Using An Example Application](#using-example-application)
- [Defining Broadcast Events](#defining-broadcast-events)
+ - [Broadcast Name](#broadcast-name)
- [Broadcast Data](#broadcast-data)
- [Broadcast Queue](#broadcast-queue)
- [Authorizing Channels](#authorizing-channels)
@@ -16,17 +17,19 @@
- [Receiving Broadcasts](#receiving-broadcasts)
- [Installing Laravel Echo](#installing-laravel-echo)
- [Listening For Events](#listening-for-events)
+ - [Leaving A Channel](#leaving-a-channel)
- [Namespaces](#namespaces)
- [Presence Channels](#presence-channels)
- [Authorizing Presence Channels](#authorizing-presence-channels)
- [Joining Presence Channels](#joining-presence-channels)
- [Broadcasting To Presence Channels](#broadcasting-to-presence-channels)
+- [Client Events](#client-events)
- [Notifications](#notifications)
## Introduction
-In many modern web applications, WebSockets are used to implement real-time, live-updating user interfaces. When some data is updated on the server, a message is typically sent over a WebSocket connection to be handled by the client. This provides a more robust, efficient alternative to continually polling your application for changes.
+In many modern web applications, WebSockets are used to implement realtime, live-updating user interfaces. When some data is updated on the server, a message is typically sent over a WebSocket connection to be handled by the client. This provides a more robust, efficient alternative to continually polling your application for changes.
To assist you in building these types of applications, Laravel makes it easy to "broadcast" your [events](/docs/{{version}}/events) over a WebSocket connection. Broadcasting your Laravel events allows you to share the same event names between your server-side code and your client-side JavaScript application.
@@ -56,9 +59,14 @@ If you are broadcasting your events over [Pusher](https://pusher.com), you shoul
composer require pusher/pusher-php-server
-Next, you should configure your Pusher credentials in the `config/broadcasting.php` configuration file. An example Pusher configuration is already included in this file, allowing you to quickly specify your Pusher key, secret, and application ID.
+Next, you should configure your Pusher credentials in the `config/broadcasting.php` configuration file. An example Pusher configuration is already included in this file, allowing you to quickly specify your Pusher key, secret, and application ID. The `config/broadcasting.php` file's `pusher` configuration also allows you to specify additional `options` that are supported by Pusher, such as the cluster:
-When using Pusher and [Laravel Echo](#installing-laravel-echo), you should specify `pusher` as your desired broadcaster when instantiating an Echo instance:
+ 'options' => [
+ 'cluster' => 'eu',
+ 'encrypted' => true
+ ],
+
+When using Pusher and [Laravel Echo](#installing-laravel-echo), you should specify `pusher` as your desired broadcaster when instantiating the Echo instance in your `resources/assets/js/bootstrap.js` file:
import Echo from "laravel-echo"
@@ -79,19 +87,17 @@ When the Redis broadcaster publishes an event, it will be published on the event
#### Socket.IO
-> {note} Socket.IO server support is currently in beta and is community driven. Public and private channels are supported; however, presence channels are not fully implemented.
-
-If you are going to pair the Redis broadcaster with a Socket.IO server, you will need to include the Socket.IO JavaScript client library in your application's `head` HTML element:
+If you are going to pair the Redis broadcaster with a Socket.IO server, you will need to include the Socket.IO JavaScript client library in your application's `head` HTML element. When the Socket.IO server is started, it will automatically expose the client JavaScript library at a standard URL. For example, if you are running the Socket.IO server on the same domain as your web application, you may access the client library like so:
-
+
-Next, you will need to instantiate Echo with the `socket.io` connector and a `host`. For example, if your application and socket server are running on the `app.dev` domain you should instantiate Echo like so:
+Next, you will need to instantiate Echo with the `socket.io` connector and a `host`.
import Echo from "laravel-echo"
window.Echo = new Echo({
broadcaster: 'socket.io',
- host: 'http://app.dev:6001'
+ host: window.location.hostname + ':6001'
});
Finally, you will need to run a compatible Socket.IO server. Laravel does not include a Socket.IO server implementation; however, a community driven Socket.IO server is currently maintained at the [tlaverdure/laravel-echo-server](https://github.com/tlaverdure/laravel-echo-server) GitHub repository.
@@ -103,14 +109,14 @@ Before broadcasting events, you will also need to configure and run a [queue lis
## Concept Overview
-Laravel's event broadcasting allows you to broadcast your server-side Laravel events to your client-side JavaScript application using a driver-based approach to WebSockets. Currently, Laravel ships with [Pusher](http://pusher.com) and Redis drivers. The events may be easily consumed on the client-side using the [Laravel Echo](#installing-laravel-echo) Javascript package.
+Laravel's event broadcasting allows you to broadcast your server-side Laravel events to your client-side JavaScript application using a driver-based approach to WebSockets. Currently, Laravel ships with [Pusher](https://pusher.com) and Redis drivers. The events may be easily consumed on the client-side using the [Laravel Echo](#installing-laravel-echo) Javascript package.
Events are broadcast over "channels", which may be specified as public or private. Any visitor to your application may subscribe to a public channel without any authentication or authorization; however, in order to subscribe to a private channel, a user must be authenticated and authorized to listen on that channel.
-### Using Example Application
+### Using An Example Application
-Before diving into each component of event broadcasting, let's take a high level overview using an e-commerce store as an example. We won't discuss the details of configuring [Pusher](http://pusher.com) or [Laravel Echo](#echo) since that will be discussed in detail in other sections of this documentation.
+Before diving into each component of event broadcasting, let's take a high level overview using an e-commerce store as an example. We won't discuss the details of configuring [Pusher](https://pusher.com) or [Laravel Echo](#installing-laravel-echo) since that will be discussed in detail in other sections of this documentation.
In our application, let's assume we have a page that allows users to view the shipping status for their orders. Let's also assume that a `ShippingStatusUpdated` event is fired when a shipping status update is processed by the application:
@@ -133,7 +139,12 @@ When a user is viewing one of their orders, we don't want them to have to refres
class ShippingStatusUpdated implements ShouldBroadcast
{
- //
+ /**
+ * Information about the shipping status update.
+ *
+ * @var string
+ */
+ public $update;
}
The `ShouldBroadcast` interface requires our event to define a `broadcastOn` method. This method is responsible for returning the channels that the event should broadcast on. An empty stub of this method is already defined on generated event classes, so we only need to fill in its details. We only want the creator of the order to be able to view status updates, so we will broadcast the event on a private channel that is tied to the order:
@@ -150,21 +161,21 @@ The `ShouldBroadcast` interface requires our event to define a `broadcastOn` met
#### Authorizing Channels
-Remember, users must be authorized to listen on private channels. We may define our channel authorization rules in the `boot` method of the `BroadcastServiceProvider`. In this example, we need to verify that any user attempting to listen on the private `order.1` channel is actually the creator of the order:
+Remember, users must be authorized to listen on private channels. We may define our channel authorization rules in the `routes/channels.php` file. In this example, we need to verify that any user attempting to listen on the private `order.1` channel is actually the creator of the order:
- Broadcast::channel('order.*', function ($user, $orderId) {
+ Broadcast::channel('order.{orderId}', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
The `channel` method accepts two arguments: the name of the channel and a callback which returns `true` or `false` indicating whether the user is authorized to listen on the channel.
-All authorization callbacks receive the currently authenticated user as their first argument and any additional wildcard parameters as their subsequent arguments. In this example, we are using the `*` character to indicate that the "ID" portion of the channel name is a wildcard.
+All authorization callbacks receive the currently authenticated user as their first argument and any additional wildcard parameters as their subsequent arguments. In this example, we are using the `{orderId}` placeholder to indicate that the "ID" portion of the channel name is a wildcard.
#### Listening For Event Broadcasts
Next, all that remains is to listen for the event in our JavaScript application. We can do this using Laravel Echo. First, we'll use the `private` method to subscribe to the private channel. Then, we may use the `listen` method to listen for the `ShippingStatusUpdated` event. By default, all of the event's public properties will be included on the broadcast event:
- Echo.private('order.' + orderId)
+ Echo.private(`order.${orderId}`)
.listen('ShippingStatusUpdated', (e) => {
console.log(e.update);
});
@@ -174,7 +185,7 @@ Next, all that remains is to listen for the event in our JavaScript application.
To inform Laravel that a given event should be broadcast, implement the `Illuminate\Contracts\Broadcasting\ShouldBroadcast` interface on the event class. This interface is already imported into all event classes generated by the framework so you may easily add it to any of your events.
-The `ShouldBroadcast` interface requires you to implement a single method: `broadcastOn`. The `broadcastOn` method should return a channel or array of channels that the event should broadcast on. The channels should be instances of `Channel`, `PrivateChannel`, or `PresenceChannel`. Instances of `Channel` represent public channels that any user my subscribe to, while `PrivateChannels` and `PresenceChannels` represent private channels that require [channel authorization](#authorizing-channels):
+The `ShouldBroadcast` interface requires you to implement a single method: `broadcastOn`. The `broadcastOn` method should return a channel or array of channels that the event should broadcast on. The channels should be instances of `Channel`, `PrivateChannel`, or `PresenceChannel`. Instances of `Channel` represent public channels that any user may subscribe to, while `PrivateChannels` and `PresenceChannels` represent private channels that require [channel authorization](#authorizing-channels):
+### Broadcast Name
+
+By default, Laravel will broadcast the event using the event's class name. However, you may customize the broadcast name by defining a `broadcastAs` method on the event:
+
+ /**
+ * The event's broadcast name.
+ *
+ * @return string
+ */
+ public function broadcastAs()
+ {
+ return 'server.created';
+ }
+
### Broadcast Data
@@ -272,15 +298,25 @@ The `Broadcast::routes` method will automatically place its routes within the `w
### Defining Authorization Callbacks
-Next, we need to define the logic that will actually perform the channel authorization. Like defining the authorization routes, this is also done in the `boot` method of the `BroadcastServiceProvider`. In this method, you may use the `Broadcast::channel` method to register channel authorization callbacks:
+Next, we need to define the logic that will actually perform the channel authorization. This is done in the `routes/channels.php` file that is included with your application. In this file, you may use the `Broadcast::channel` method to register channel authorization callbacks:
- Broadcast::channel('order.*', function ($user, $orderId) {
+ Broadcast::channel('order.{orderId}', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
The `channel` method accepts two arguments: the name of the channel and a callback which returns `true` or `false` indicating whether the user is authorized to listen on the channel.
-All authorization callbacks receive the currently authenticated user as their first argument and any additional wildcard parameters as their subsequent arguments. In this example, we are using the `*` character to indicate that the "ID" portion of the channel name is a wildcard.
+All authorization callbacks receive the currently authenticated user as their first argument and any additional wildcard parameters as their subsequent arguments. In this example, we are using the `{orderId}` placeholder to indicate that the "ID" portion of the channel name is a wildcard.
+
+#### Authorization Callback Model Binding
+
+Just like HTTP routes, channel routes may also take advantage of implicit and explicit [route model binding](/docs/{{version}}/routing#route-model-binding). For example, instead of receiving the string or numeric order ID, you may request an actual `Order` model instance:
+
+ use App\Order;
+
+ Broadcast::channel('order.{order}', function ($user, Order $order) {
+ return $user->id === $order->user_id;
+ });
## Broadcasting Events
@@ -302,7 +338,7 @@ However, the `broadcast` function also exposes the `toOthers` method which allow
To better understand when you may want to use the `toOthers` method, let's imagine a task list application where a user may create a new task by entering a task name. To create a task, your application might make a request to a `/task` end-point which broadcasts the task's creation and returns a JSON representation of the new task. When your JavaScript application receives the response from the end-point, it might directly insert the new task into its task list like so:
- this.$http.post('/task', task)
+ axios.post('/task', task)
.then((response) => {
this.tasks.push(response.data);
});
@@ -313,9 +349,9 @@ You may solve this by using the `toOthers` method to instruct the broadcaster to
#### Configuration
-When you initialize a Laravel Echo instance, a socket ID is assigned to the connection. If you are using [Vue](https://vuejs.org) and Vue Resource, the socket ID will automatically be attached to every outgoing request as a `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 [Vue](https://vuejs.org) and [Axios](https://github.com/mzabriskie/axios), the socket ID will automatically be attached to every outgoing request as a `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 Vue and Vue Resource, you will need to manually configure your JavaScript application to send the `X-Socket-ID` header. You may retrieve the socket ID using the `Echo.socketId` method:
+If you are not using Vue and Axios, you will need to manually configure your JavaScript application to send the `X-Socket-ID` header. You may retrieve the socket ID using the `Echo.socketId` method:
var socketId = Echo.socketId();
@@ -338,6 +374,15 @@ Once Echo is installed, you are ready to create a fresh Echo instance in your ap
key: 'your-pusher-key'
});
+When creating an Echo instance that uses the `pusher` connector, you may also specify a `cluster` as well as whether the connection should be encrypted:
+
+ window.Echo = new Echo({
+ broadcaster: 'pusher',
+ key: 'your-pusher-key',
+ cluster: 'eu',
+ encrypted: true
+ });
+
### Listening For Events
@@ -355,6 +400,13 @@ If you would like to listen for events on a private channel, use the `private` m
.listen(...)
.listen(...);
+
+### Leaving A Channel
+
+To leave a channel, you may call the `leave` method on your Echo instance:
+
+ Echo.leave('orders');
+
### Namespaces
@@ -396,7 +448,7 @@ The data returned by the authorization callback will be made available to the pr
To join a presence channel, you may use Echo's `join` method. The `join` method will return a `PresenceChannel` implementation which, along with exposing the `listen` method, allows you to subscribe to the `here`, `joining`, and `leaving` events.
- Echo.join('chat.' + roomId)
+ Echo.join(`chat.${roomId}`)
.here((users) => {
//
})
@@ -432,7 +484,7 @@ Like public or private events, presence channel events may be broadcast using th
You may listen for the join event via Echo's `listen` method:
- Echo.join('chat.' + roomId)
+ Echo.join(`chat.${roomId}`)
.here(...)
.joining(...)
.leaving(...)
@@ -440,6 +492,23 @@ You may listen for the join event via Echo's `listen` method:
//
});
+
+## Client Events
+
+Sometimes you may wish to broadcast an event to other connected clients without hitting your Laravel application at all. This can be particularly useful for things like "typing" notifications, where you want to alert users of your application that another user is typing a message on a given screen. To broadcast client events, you may use Echo's `whisper` method:
+
+ Echo.channel('chat')
+ .whisper('typing', {
+ name: this.user.name
+ });
+
+To listen for client events, you may use the `listenForWhisper` method:
+
+ Echo.channel('chat')
+ .listenForWhisper('typing', (e) => {
+ console.log(e.name);
+ });
+
## Notifications
@@ -447,9 +516,9 @@ By pairing event broadcasting with [notifications](/docs/{{version}}/notificatio
Once you have configured a notification to use the broadcast channel, you may listen for the broadcast events using Echo's `notification` method. Remember, the channel name should match the class name of the entity receiving the notifications:
- Echo.private('App.User.' + userId)
+ Echo.private(`App.User.${userId}`)
.notification((notification) => {
console.log(notification.type);
});
-In this example, all notifications sent to `App\User` instances via the `broadcast` channel would be received by the callback. A channel authorization callback for the `App.User.*` channel is included in the default `BroadcastServiceProvider` that ships with the Laravel framework.
+In this example, all notifications sent to `App\User` instances via the `broadcast` channel would be received by the callback. A channel authorization callback for the `App.User.{id}` channel is included in the default `BroadcastServiceProvider` that ships with the Laravel framework.
diff --git a/cache.md b/cache.md
index d3039f0..199f92d 100644
--- a/cache.md
+++ b/cache.md
@@ -7,6 +7,7 @@
- [Retrieving Items From The Cache](#retrieving-items-from-the-cache)
- [Storing Items In The Cache](#storing-items-in-the-cache)
- [Removing Items From The Cache](#removing-items-from-the-cache)
+ - [The Cache Helper](#the-cache-helper)
- [Cache Tags](#cache-tags)
- [Storing Tagged Cache Items](#storing-tagged-cache-items)
- [Accessing Tagged Cache Items](#accessing-tagged-cache-items)
@@ -19,7 +20,7 @@
## Configuration
-Laravel provides an expressive, unified API for various caching backends. The cache configuration is located at `config/cache.php`. In this file you may specify which cache driver you would like used by default throughout your application. Laravel supports popular caching backends like [Memcached](http://memcached.org) and [Redis](http://redis.io) out of the box.
+Laravel provides an expressive, unified API for various caching backends. The cache configuration is located at `config/cache.php`. In this file you may specify which cache driver you would like used by default throughout your application. Laravel supports popular caching backends like [Memcached](https://memcached.org) and [Redis](http://redis.io) out of the box.
The cache configuration file also contains various other options, which are documented within the file, so make sure to read over these options. By default, Laravel is configured to use the `file` cache driver, which stores the serialized, cached objects in the filesystem. For larger applications, it is recommended that you use a more robust driver such as Memcached or Redis. You may even configure multiple cache configurations for the same driver.
@@ -30,7 +31,7 @@ The cache configuration file also contains various other options, which are docu
When using the `database` cache driver, you will need to setup a table to contain the cache items. You'll find an example `Schema` declaration for the table below:
- Schema::create('cache', function($table) {
+ Schema::create('cache', function ($table) {
$table->string('key')->unique();
$table->text('value');
$table->integer('expiration');
@@ -40,7 +41,7 @@ When using the `database` cache driver, you will need to setup a table to contai
#### Memcached
-Using the Memcached driver requires the [Memcached PECL package](http://pecl.php.net/package/memcached) to be installed. You may list all of your Memcached servers in the `config/cache.php` configuration file:
+Using the Memcached driver requires the [Memcached PECL package](https://pecl.php.net/package/memcached) to be installed. You may list all of your Memcached servers in the `config/cache.php` configuration file:
'memcached' => [
[
@@ -62,7 +63,7 @@ You may also set the `host` option to a UNIX socket path. If you do this, the `p
#### Redis
-Before using a Redis cache with Laravel, you will need to install the `predis/predis` package (~1.0) via Composer.
+Before using a Redis cache with Laravel, you will need to either install the `predis/predis` package (~1.0) via Composer or install the PhpRedis PHP extension via PECL.
For more information on configuring Redis, consult its [Laravel documentation page](/docs/{{version}}/redis#configuration).
@@ -114,16 +115,15 @@ The `get` method on the `Cache` facade is used to retrieve items from the cache.
$value = Cache::get('key', 'default');
-
You may even pass a `Closure` as the default value. The result of the `Closure` will be returned if the specified item does not exist in the cache. Passing a Closure allows you to defer the retrieval of default values from a database or other external service:
- $value = Cache::get('key', function() {
+ $value = Cache::get('key', function () {
return DB::table(...)->get();
});
#### Checking For Item Existence
-The `has` method may be used to determine if an item exists in the cache:
+The `has` method may be used to determine if an item exists in the cache. This method will return `false` if the value is `null` or `false`:
if (Cache::has('key')) {
//
@@ -142,7 +142,7 @@ The `increment` and `decrement` methods may be used to adjust the value of integ
Sometimes you may wish to retrieve an item from the cache, but also store a default value if the requested item doesn't exist. For example, you may wish to retrieve all users from the cache or, if they don't exist, retrieve them from the database and add them to the cache. You may do this using the `Cache::remember` method:
- $value = Cache::remember('users', $minutes, function() {
+ $value = Cache::remember('users', $minutes, function () {
return DB::table('users')->get();
});
@@ -194,6 +194,21 @@ You may clear the entire cache using the `flush` method:
> {note} Flushing the cache does not respect the cache prefix and will remove all entries from the cache. Consider this carefully when clearing a cache which is shared by other applications.
+
+### The Cache Helper
+
+In addition to using the `Cache` facade or [cache contract](/docs/{{version}}/contracts), you may also use the global `cache` function to retrieve and store data via the cache. When the `cache` function is called with a single, string argument, it will return the value of the given key:
+
+ $value = cache('key');
+
+If you provide an array of key / value pairs and an expiration time to the function, it will store values in the cache for the specified duration:
+
+ cache(['key' => 'value'], $minutes);
+
+ cache(['key' => 'value'], Carbon::now()->addSeconds(10));
+
+> {tip} When testing call to the global `cache` function, you may use the `Cache::shouldReceive` method just as if you were [testing a facade](/docs/{{version}}/mocking#mocking-facades).
+
## Cache Tags
@@ -204,16 +219,16 @@ You may clear the entire cache using the `flush` method:
Cache tags allow you to tag related items in the cache and then flush all cached values that have been assigned a given tag. You may access a tagged cache by passing in an ordered array of tag names. For example, let's access a tagged cache and `put` value in the cache:
- Cache::tags(['people', 'artists'])->put('John', $john, $minutes);
+ Cache::tags(['people', 'artists'])->put('John', $john, $minutes);
- Cache::tags(['people', 'authors'])->put('Anne', $anne, $minutes);
+ Cache::tags(['people', 'authors'])->put('Anne', $anne, $minutes);
### Accessing Tagged Cache Items
To retrieve a tagged cache item, pass the same ordered list of tags to the `tags` method and then call the `get` method with the key you wish to retrieve:
- $john = Cache::tags(['people', 'artists'])->get('John');
+ $john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');
@@ -222,11 +237,11 @@ To retrieve a tagged cache item, pass the same ordered list of tags to the `tags
You may flush all items that are assigned a tag or list of tags. For example, this statement would remove all caches tagged with either `people`, `authors`, or both. So, both `Anne` and `John` would be removed from the cache:
- Cache::tags(['people', 'authors'])->flush();
+ Cache::tags(['people', 'authors'])->flush();
In contrast, this statement would remove only caches tagged with `authors`, so `Anne` would be removed, but not `John`:
- Cache::tags('authors')->flush();
+ Cache::tags('authors')->flush();
## Adding Custom Cache Drivers
@@ -258,7 +273,7 @@ To create our custom cache driver, we first need to implement the `Illuminate\Co
We just need to implement each of these methods using a MongoDB connection. For an example of how to implement each of these methods, take a look at the `Illuminate\Cache\MemcachedStore` in the framework source code. Once our implementation is complete, we can finish our custom driver registration.
- Cache::extend('mongo', function($app) {
+ Cache::extend('mongo', function ($app) {
return Cache::repository(new MongoStore);
});
@@ -286,7 +301,7 @@ To register the custom cache driver with Laravel, we will use the `extend` metho
*/
public function boot()
{
- Cache::extend('mongo', function($app) {
+ Cache::extend('mongo', function ($app) {
return Cache::repository(new MongoStore);
});
}
diff --git a/collections.md b/collections.md
index 808aa49..e71060b 100644
--- a/collections.md
+++ b/collections.md
@@ -3,6 +3,7 @@
- [Introduction](#introduction)
- [Creating Collections](#creating-collections)
- [Available Methods](#available-methods)
+- [Higher Order Messages](#higher-order-messages)
## Introduction
@@ -75,10 +76,14 @@ For the remainder of this documentation, we'll discuss each method available on
[keys](#method-keys)
[last](#method-last)
[map](#method-map)
+[mapWithKeys](#method-mapwithkeys)
[max](#method-max)
[merge](#method-merge)
[min](#method-min)
+[nth](#method-nth)
[only](#method-only)
+[partition](#method-partition)
+[pipe](#method-pipe)
[pluck](#method-pluck)
[pop](#method-pop)
[prepend](#method-prepend)
@@ -97,6 +102,7 @@ For the remainder of this documentation, we'll discuss each method available on
[sortBy](#method-sortby)
[sortByDesc](#method-sortbydesc)
[splice](#method-splice)
+[split](#method-split)
[sum](#method-sum)
[take](#method-take)
[toArray](#method-toarray)
@@ -108,7 +114,7 @@ For the remainder of this documentation, we'll discuss each method available on
[where](#method-where)
[whereStrict](#method-wherestrict)
[whereIn](#method-wherein)
-[whereInLoose](#method-whereinloose)
+[whereInStrict](#method-whereinstrict)
[zip](#method-zip)
@@ -168,7 +174,7 @@ The `chunk` method breaks the collection into multiple, smaller collections of a
// [[1, 2, 3, 4], [5, 6, 7]]
-This method is especially useful in [views](/docs/{{version}}/views) when working with a grid system such as [Bootstrap](http://getbootstrap.com/css/#grid). Imagine you have a collection of [Eloquent](/docs/{{version}}/eloquent) models you want to display in a grid:
+This method is especially useful in [views](/docs/{{version}}/views) when working with a grid system such as [Bootstrap](https://getbootstrap.com/css/#grid). Imagine you have a collection of [Eloquent](/docs/{{version}}/eloquent) models you want to display in a grid:
@foreach ($products->chunk(3) as $chunk)
@@ -308,19 +314,13 @@ If you would like to stop iterating through the items, you may return `false` fr
#### `every()` {#collection-method}
-The `every` method creates a new collection consisting of every n-th element:
+The `every` method may be used to verify that all elements of a collection pass a given truth test:
- $collection = collect(['a', 'b', 'c', 'd', 'e', 'f']);
-
- $collection->every(4);
-
- // ['a', 'e']
-
-You may optionally pass an offset as the second argument:
-
- $collection->every(4, 1);
+ collect([1, 2, 3, 4])->every(function ($value, $key) {
+ return $value > 2;
+ });
- // ['b', 'f']
+ // false
#### `except()` {#collection-method}
@@ -352,6 +352,14 @@ The `filter` method filters the collection using the given callback, keeping onl
// [3, 4]
+If no callback is supplied, all entries of the collection that are equivalent to `false` will be removed:
+
+ $collection = collect([1, 2, 3, null, false, '', 0, []]);
+
+ $collection->filter()->all();
+
+ // [1, 2, 3]
+
For the inverse of `filter`, see the [reject](#method-reject) method.
@@ -549,9 +557,9 @@ The `has` method determines if a given key exists in the collection:
$collection = collect(['account_id' => 1, 'product' => 'Desk']);
- $collection->has('email');
+ $collection->has('product');
- // false
+ // true
#### `implode()` {#collection-method}
@@ -682,10 +690,41 @@ The `map` method iterates through the collection and passes each value to the gi
> {note} 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.
+
+#### `mapWithKeys()` {#collection-method}
+
+The `mapWithKeys` method iterates through the collection and passes each value to the given callback. The callback should return an associative array containing a single key / value pair:
+
+ $collection = collect([
+ [
+ 'name' => 'John',
+ 'department' => 'Sales',
+ 'email' => 'john@example.com'
+ ],
+ [
+ 'name' => 'Jane',
+ 'department' => 'Marketing',
+ 'email' => 'jane@example.com'
+ ]
+ ]);
+
+ $keyed = $collection->mapWithKeys(function ($item) {
+ return [$item['email'] => $item['name']];
+ });
+
+ $keyed->all();
+
+ /*
+ [
+ 'john@example.com' => 'John',
+ 'jane@example.com' => 'Jane',
+ ]
+ */
+
#### `max()` {#collection-method}
-The `max` method return the maximum value of a given key:
+The `max` method returns the maximum value of a given key:
$max = collect([['foo' => 10], ['foo' => 20]])->max('foo');
@@ -698,7 +737,7 @@ The `max` method return the maximum value of a given key:
#### `merge()` {#collection-method}
-The `merge` method merges the given array into the original collection. If a string key in the given array matches a string key in the original collection, the given array's value will overwrite the value in the original collection:
+The `merge` method merges the given array with the original collection. If a string key in the given array matches a string key in the original collection, the given array's value will overwrite the value in the original collection:
$collection = collect(['product_id' => 1, 'price' => 100]);
@@ -706,7 +745,7 @@ The `merge` method merges the given array into the original collection. If a str
$merged->all();
- // ['product_id' => 1, price' => 200, 'discount' => false]
+ // ['product_id' => 1, 'price' => 200, 'discount' => false]
If the given array's keys are numeric, the values will be appended to the end of the collection:
@@ -721,7 +760,7 @@ If the given array's keys are numeric, the values will be appended to the end of
#### `min()` {#collection-method}
-The `min` method return the minimum value of a given key:
+The `min` method returns the minimum value of a given key:
$min = collect([['foo' => 10], ['foo' => 20]])->min('foo');
@@ -731,6 +770,23 @@ The `min` method return the minimum value of a given key:
// 1
+
+#### `nth()` {#collection-method}
+
+The `nth` method creates a new collection consisting of every n-th element:
+
+ $collection = collect(['a', 'b', 'c', 'd', 'e', 'f']);
+
+ $collection->nth(4);
+
+ // ['a', 'e']
+
+You may optionally pass an offset as the second argument:
+
+ $collection->nth(4, 1);
+
+ // ['b', 'f']
+
#### `only()` {#collection-method}
@@ -746,6 +802,30 @@ The `only` method returns the items in the collection with the specified keys:
For the inverse of `only`, see the [except](#method-except) method.
+
+#### `partition()` {#collection-method}
+
+The `partition` method may be combined with the `list` PHP function to separate elements that pass a given truth test from those that do not:
+
+ $collection = collect([1, 2, 3, 4, 5, 6]);
+
+ list($underThree, $aboveThree) = $collection->partition(function ($i) {
+ return $i < 3;
+ });
+
+
+#### `pipe()` {#collection-method}
+
+The `pipe` method passes the collection to the given callback and returns the result:
+
+ $collection = collect([1, 2, 3]);
+
+ $piped = $collection->pipe(function ($collection) {
+ return $collection->sum();
+ });
+
+ // 6
+
#### `pluck()` {#collection-method}
@@ -860,7 +940,7 @@ The `random` method returns a random item from the collection:
// 4 - (retrieved randomly)
-You may optionally pass an integer to `random` to specify how many items you would like to randomly retrieve. If that integer is more than `1`, a collection of items is returned:
+You may optionally pass an integer to `random` to specify how many items you would like to randomly retrieve. A collection of items is always returned when explicitly passing the number of items you wish to receive:
$random = $collection->random(3);
@@ -1008,7 +1088,7 @@ The `sort` method sorts the collection. The sorted collection keeps the original
// [1, 2, 3, 4, 5]
-If your sorting needs are more advanced, you may pass a callback to `sort` with your own algorithm. Refer to the PHP documentation on [`usort`](http://php.net/manual/en/function.usort.php#refsect1-function.usort-parameters), which is what the collection's `sort` method calls under the hood.
+If your sorting needs are more advanced, you may pass a callback to `sort` with your own algorithm. Refer to the PHP documentation on [`usort`](https://secure.php.net/manual/en/function.usort.php#refsect1-function.usort-parameters), which is what the collection's `sort` method calls under the hood.
> {tip} If you need to sort a collection of nested arrays or objects, see the [`sortBy`](#method-sortby) and [`sortByDesc`](#method-sortbydesc) methods.
@@ -1107,6 +1187,19 @@ In addition, you can pass a third argument containing the new items to replace t
// [1, 2, 10, 11, 4, 5]
+
+#### `split()` {#collection-method}
+
+The `split` method breaks a collection into the given number of groups:
+
+ $collection = collect([1, 2, 3, 4, 5]);
+
+ $groups = $collection->split(3);
+
+ $groups->toArray();
+
+ // [[1, 2], [3, 4], [5]]
+
#### `sum()` {#collection-method}
@@ -1190,7 +1283,7 @@ The `toJson` method converts the collection into JSON:
$collection->toJson();
- // '{"name":"Desk","price":200}'
+ // '{"name":"Desk", "price":200}'
#### `transform()` {#collection-method}
@@ -1220,7 +1313,7 @@ The `union` method adds the given array to the collection. If the given array co
$union->all();
- // [1 => ['a'], 2 => ['b'], [3 => ['c']]
+ // [1 => ['a'], 2 => ['b'], 3 => ['c']]
#### `unique()` {#collection-method}
@@ -1346,12 +1439,12 @@ The `whereIn` method filters the collection by a given key / value contained wit
]
*/
-The `whereIn` method uses strict comparisons when checking item values. Use the [`whereInLoose`](#method-whereinloose) method to filter using "loose" comparisons.
+The `whereIn` method uses "loose" comparisons when checking item values. Use the [`whereInStrict`](#method-whereinstrict) method to filter using strict comparisons.
-
-#### `whereInLoose()` {#collection-method}
+
+#### `whereInStrict()` {#collection-method}
-This method has the same signature as the [`whereIn`](#method-wherein) method; however, all values are compared using "loose" comparisons.
+This method has the same signature as the [`whereIn`](#method-wherein) method; however, all values are compared using strict comparisons.
#### `zip()` {#collection-method}
@@ -1365,3 +1458,20 @@ The `zip` method merges together the values of the given array with the values o
$zipped->all();
// [['Chair', 100], ['Desk', 200]]
+
+
+## Higher Order Messages
+
+Collections also provide support for "higher order messages", which are short-cuts for performing common actions on collections. The collection methods that provide higher order messages are: `contains`, `each`, `every`, `filter`, `first`, `map`, `partition`, `reject`, `sortBy`, `sortByDesc`, and `sum`.
+
+Each higher order message can be accessed as a dynamic property on a collection instance. For instance, let's use the `each` higher order message to call a method on each object within a collection:
+
+ $users = User::where('votes', '>', 500)->get();
+
+ $users->each->markAsVip();
+
+Likewise, we can use the `sum` higher order message to gather the total number of "votes" for a collection of users:
+
+ $users = User::where('group', 'Development')->get();
+
+ return $users->sum->votes;
diff --git a/configuration.md b/configuration.md
index d681f80..a72764b 100644
--- a/configuration.md
+++ b/configuration.md
@@ -1,9 +1,9 @@
# Configuration
- [Introduction](#introduction)
-- [Accessing Configuration Values](#accessing-configuration-values)
- [Environment Configuration](#environment-configuration)
- [Determining The Current Environment](#determining-the-current-environment)
+- [Accessing Configuration Values](#accessing-configuration-values)
- [Configuration Caching](#configuration-caching)
- [Maintenance Mode](#maintenance-mode)
@@ -12,17 +12,6 @@
All of the configuration files for the Laravel framework are stored in the `config` directory. Each option is documented, so feel free to look through the files and get familiar with the options available to you.
-
-## Accessing Configuration Values
-
-You may easily access your configuration values using the global `config` helper function from anywhere in your application. The configuration values may be accessed using "dot" syntax, which includes the name of the file and option you wish to access. A default value may also be specified and will be returned if the configuration option does not exist:
-
- $value = config('app.timezone');
-
-To set configuration values at runtime, pass an array to the `config` helper:
-
- config(['app.timezone' => 'America/Chicago']);
-
## Environment Configuration
@@ -30,6 +19,8 @@ It is often helpful to have different configuration values based on the environm
To make this a cinch, Laravel utilizes the [DotEnv](https://github.com/vlucas/phpdotenv) PHP library by Vance Lucas. In a fresh Laravel installation, the root directory of your application will contain a `.env.example` file. If you install Laravel via Composer, this file will automatically be renamed to `.env`. Otherwise, you should rename the file manually.
+> {tip} You may also create a `.env.testing` file. This file will override values from the `.env` file when running PHPUnit tests or executing Artisan commands with the `--env=testing` option.
+
#### Retrieving Environment Configuration
All of the variables listed in this file will be loaded into the `$_ENV` PHP super-global when your application receives a request. However, you may use the `env` helper to retrieve values from these variables in your configuration files. In fact, if you review the Laravel configuration files, you will notice several of the options already using this helper:
@@ -59,6 +50,17 @@ You may also pass arguments to the `environment` method to check if the environm
// The environment is either local OR staging...
}
+
+## Accessing Configuration Values
+
+You may easily access your configuration values using the global `config` helper function from anywhere in your application. The configuration values may be accessed using "dot" syntax, which includes the name of the file and option you wish to access. A default value may also be specified and will be returned if the configuration option does not exist:
+
+ $value = config('app.timezone');
+
+To set configuration values at runtime, pass an array to the `config` helper:
+
+ config(['app.timezone' => 'America/Chicago']);
+
## Configuration Caching
@@ -66,6 +68,8 @@ To give your application a speed boost, you should cache all of your configurati
You should typically run the `php artisan config:cache` command as part of your production deployment routine. The command should not be run during local development as configuration options will frequently need to be changed during the course of your application's development.
+> {note} If you execute the `config:cache` command during your deployment process, you should be sure that you are only calling the `env` function from within your configuration files.
+
## Maintenance Mode
@@ -77,7 +81,7 @@ To enable maintenance mode, simply execute the `down` Artisan command:
You may also provide `message` and `retry` options to the `down` command. The `message` value may be used to display or log a custom message, while the `retry` value will be set as the `Retry-After` HTTP header's value:
- php artisan down --message='Upgrading Database' --retry=60
+ php artisan down --message="Upgrading Database" --retry=60
To disable maintenance mode, use the `up` command:
diff --git a/container.md b/container.md
index 3b4bbe1..5431105 100644
--- a/container.md
+++ b/container.md
@@ -186,9 +186,9 @@ You may use the `make` method to resolve a class instance out of the container.
$api = $this->app->make('HelpSpot\API');
-If you are in a location of your code that does not have access to the `$app` variable, you may use the global `app` helper:
+If you are in a location of your code that does not have access to the `$app` variable, you may use the global `resolve` helper:
- $api = app('HelpSpot\API');
+ $api = resolve('HelpSpot\API');
#### Automatic Injection
diff --git a/contracts.md b/contracts.md
index 8803e40..4d8a820 100644
--- a/contracts.md
+++ b/contracts.md
@@ -31,7 +31,7 @@ Unlike facades, which do not require you to require them in your class' construc
As discussed elsewhere, much of the decision to use contracts or facades will come down to personal taste and the tastes of your development team. Both contracts and facades can be used to create robust, well-tested Laravel applications. As long as you are keeping your class' responsibilities focused, you will notice very few practical differences between using contracts and facades.
-However, you may still have several questions regarding contracts. For example, why use interfaces at all? Isn't using interfaces more complicated? Let's distil the reasons for using interfaces to the following headings: loose coupling and simplicity.
+However, you may still have several questions regarding contracts. For example, why use interfaces at all? Isn't using interfaces more complicated? Let's distill the reasons for using interfaces to the following headings: loose coupling and simplicity.
### Loose Coupling
@@ -170,36 +170,36 @@ This table provides a quick reference to all of the Laravel contracts and their
Contract | References Facade
------------- | -------------
-[Illuminate\Contracts\Auth\Factory](https://github.com/illuminate/contracts/blob/master/Auth/Factory.php) | Auth
-[Illuminate\Contracts\Auth\PasswordBroker](https://github.com/illuminate/contracts/blob/master/Auth/PasswordBroker.php) | Password
-[Illuminate\Contracts\Bus\Dispatcher](https://github.com/illuminate/contracts/blob/master/Bus/Dispatcher.php) | Bus
-[Illuminate\Contracts\Broadcasting\Broadcaster](https://github.com/illuminate/contracts/blob/master/Broadcasting/Broadcaster.php) |
-[Illuminate\Contracts\Cache\Repository](https://github.com/illuminate/contracts/blob/master/Cache/Repository.php) | Cache
-[Illuminate\Contracts\Cache\Factory](https://github.com/illuminate/contracts/blob/master/Cache/Factory.php) | Cache::driver()
-[Illuminate\Contracts\Config\Repository](https://github.com/illuminate/contracts/blob/master/Config/Repository.php) | Config
-[Illuminate\Contracts\Container\Container](https://github.com/illuminate/contracts/blob/master/Container/Container.php) | App
-[Illuminate\Contracts\Cookie\Factory](https://github.com/illuminate/contracts/blob/master/Cookie/Factory.php) | Cookie
-[Illuminate\Contracts\Cookie\QueueingFactory](https://github.com/illuminate/contracts/blob/master/Cookie/QueueingFactory.php) | Cookie::queue()
-[Illuminate\Contracts\Encryption\Encrypter](https://github.com/illuminate/contracts/blob/master/Encryption/Encrypter.php) | Crypt
-[Illuminate\Contracts\Events\Dispatcher](https://github.com/illuminate/contracts/blob/master/Events/Dispatcher.php) | Event
-[Illuminate\Contracts\Filesystem\Cloud](https://github.com/illuminate/contracts/blob/master/Filesystem/Cloud.php) |
-[Illuminate\Contracts\Filesystem\Factory](https://github.com/illuminate/contracts/blob/master/Filesystem/Factory.php) | File
-[Illuminate\Contracts\Filesystem\Filesystem](https://github.com/illuminate/contracts/blob/master/Filesystem/Filesystem.php) | File
-[Illuminate\Contracts\Foundation\Application](https://github.com/illuminate/contracts/blob/master/Foundation/Application.php) | App
-[Illuminate\Contracts\Hashing\Hasher](https://github.com/illuminate/contracts/blob/master/Hashing/Hasher.php) | Hash
-[Illuminate\Contracts\Logging\Log](https://github.com/illuminate/contracts/blob/master/Logging/Log.php) | Log
-[Illuminate\Contracts\Mail\MailQueue](https://github.com/illuminate/contracts/blob/master/Mail/MailQueue.php) | Mail::queue()
-[Illuminate\Contracts\Mail\Mailer](https://github.com/illuminate/contracts/blob/master/Mail/Mailer.php) | Mail
-[Illuminate\Contracts\Queue\Factory](https://github.com/illuminate/contracts/blob/master/Queue/Factory.php) | Queue::driver()
-[Illuminate\Contracts\Queue\Queue](https://github.com/illuminate/contracts/blob/master/Queue/Queue.php) | Queue
-[Illuminate\Contracts\Redis\Database](https://github.com/illuminate/contracts/blob/master/Redis/Database.php) | Redis
-[Illuminate\Contracts\Routing\Registrar](https://github.com/illuminate/contracts/blob/master/Routing/Registrar.php) | Route
-[Illuminate\Contracts\Routing\ResponseFactory](https://github.com/illuminate/contracts/blob/master/Routing/ResponseFactory.php) | Response
-[Illuminate\Contracts\Routing\UrlGenerator](https://github.com/illuminate/contracts/blob/master/Routing/UrlGenerator.php) | URL
-[Illuminate\Contracts\Support\Arrayable](https://github.com/illuminate/contracts/blob/master/Support/Arrayable.php) |
-[Illuminate\Contracts\Support\Jsonable](https://github.com/illuminate/contracts/blob/master/Support/Jsonable.php) |
-[Illuminate\Contracts\Support\Renderable](https://github.com/illuminate/contracts/blob/master/Support/Renderable.php) |
-[Illuminate\Contracts\Validation\Factory](https://github.com/illuminate/contracts/blob/master/Validation/Factory.php) | Validator::make()
-[Illuminate\Contracts\Validation\Validator](https://github.com/illuminate/contracts/blob/master/Validation/Validator.php) |
-[Illuminate\Contracts\View\Factory](https://github.com/illuminate/contracts/blob/master/View/Factory.php) | View::make()
-[Illuminate\Contracts\View\View](https://github.com/illuminate/contracts/blob/master/View/View.php) |
+[Illuminate\Contracts\Auth\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Factory.php) | Auth
+[Illuminate\Contracts\Auth\PasswordBroker](https://github.com/illuminate/contracts/blob/{{version}}/Auth/PasswordBroker.php) | Password
+[Illuminate\Contracts\Bus\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Bus/Dispatcher.php) | Bus
+[Illuminate\Contracts\Broadcasting\Broadcaster](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/Broadcaster.php) |
+[Illuminate\Contracts\Cache\Repository](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Repository.php) | Cache
+[Illuminate\Contracts\Cache\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Factory.php) | Cache::driver()
+[Illuminate\Contracts\Config\Repository](https://github.com/illuminate/contracts/blob/{{version}}/Config/Repository.php) | Config
+[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\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) |
+[Illuminate\Contracts\Filesystem\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Factory.php) | File
+[Illuminate\Contracts\Filesystem\Filesystem](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Filesystem.php) | File
+[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\Logging\Log](https://github.com/illuminate/contracts/blob/{{version}}/Logging/Log.php) | Log
+[Illuminate\Contracts\Mail\MailQueue](https://github.com/illuminate/contracts/blob/{{version}}/Mail/MailQueue.php) | Mail::queue()
+[Illuminate\Contracts\Mail\Mailer](https://github.com/illuminate/contracts/blob/{{version}}/Mail/Mailer.php) | Mail
+[Illuminate\Contracts\Queue\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Factory.php) | Queue::driver()
+[Illuminate\Contracts\Queue\Queue](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Queue.php) | Queue
+[Illuminate\Contracts\Redis\Database](https://github.com/illuminate/contracts/blob/{{version}}/Redis/Database.php) | Redis
+[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\Support\Arrayable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Arrayable.php) |
+[Illuminate\Contracts\Support\Jsonable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Jsonable.php) |
+[Illuminate\Contracts\Support\Renderable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Renderable.php) |
+[Illuminate\Contracts\Validation\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Factory.php) | Validator::make()
+[Illuminate\Contracts\Validation\Validator](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Validator.php) |
+[Illuminate\Contracts\View\Factory](https://github.com/illuminate/contracts/blob/{{version}}/View/Factory.php) | View::make()
+[Illuminate\Contracts\View\View](https://github.com/illuminate/contracts/blob/{{version}}/View/View.php) |
diff --git a/contributing.md b/contributing.md
index e68c3d3..266c057 100644
--- a/contributing.md
+++ b/contributing.md
@@ -1,3 +1,17 @@
-# Contribution Guidelines
+# ပူးပေါင်းပါဝင်မှု လမ်းညွန်
-If you are submitting documentation for the **current stable release**, submit it to the corresponding branch. For example, documentation for Laravel 5.1 would be submitted to the `5.1` branch. Documentation intended for the next release of Laravel should be submitted to the `master` branch.
\ No newline at end of file
+###ဘာသာပြန်ခြင်း
+
+ - ကျွန်တော်တို့ကို ပူးပေါင်းပါဝင် ကူညီပြီးဘာသာပြန်ချင်တယ်ဆိုရင်
+ [docs](https://github.com/setkyar/laravel-docs/) ကိုဦးစွာ Fork လုပ်ပါ၊
+
+ - ဘယ်အပိုင်းကို ဘာသာပြန်မည်ဆိုတာကို [Facebook](https://www.facebook.com/groups/250409601822202/) မှာပြောပေးပါ။ (Fork
+ လုပ်ပြီးဘာသာပြန်နေတုန်းအခြားတစ်ယောက်ယောက်ကပါဘာသာပြန်နေတာမျိုးဖြစ်မှာစိုးလို့ပါ)
+
+ - သင်ဘာသာပြန်မည့် File ကိုဘာသာပြန်ပါ။ ဘာသာပြန်ပြီးရင် မူရင်း repo ဆီက
+ [pull request](https://github.com/setkyar/laravel-docs/pulls) တောင်းပါ။ (pull request တောင်းတာကို မြန်မြန် accept
+ လုပ်စေချင်တယ်ဆိုရင်[Facebook]( https://www.facebook.com/groups/250409601822202/) မှာပါတင်ပေးပါ)
+
+###ဘာသာပြန်အဆင်ပြေမှူနှင့် စာလုံးပေါင်းအမှား
+
+ဘာသာပြန်ထားတာတွေ ကိုဦးစွာဖတ်ပါ။ ဘာသာပြန်အဆင်ပြေမှူ နဲ့ စာလုံးပေါင်း အမှားတွေကို စစ်ပါ။ ဘာသာပြန်အဆင်ပြေမှူမရှိတာတို့ စာလုံးပေါင်းအမှားတွေတွေ့ရင် GitHub မှာ [issue](https://github.com/setkyar/laravel-docs/issues) တင်ပေးပါ။(အမှားတွေကို အမြန်ဆုံး စစ်ပေးဖို့ [Facebook](https://www.facebook.com/groups/250409601822202/) မှာပါတင်ပေးပါ)
diff --git a/contributions.md b/contributions.md
index fd80e2c..9c94c09 100644
--- a/contributions.md
+++ b/contributions.md
@@ -27,6 +27,9 @@ The Laravel source code is managed on Github, and there are repositories for eac
- [Laravel Envoy](https://github.com/laravel/envoy)
- [Laravel Homestead](https://github.com/laravel/homestead)
- [Laravel Homestead Build Scripts](https://github.com/laravel/settler)
+- [Laravel Passport](https://github.com/laravel/passport)
+- [Laravel Scout](https://github.com/laravel/scout)
+- [Laravel Socialite](https://github.com/laravel/socialite)
- [Laravel Website](https://github.com/laravel/laravel.com)
- [Laravel Art](https://github.com/laravel/art)
@@ -35,7 +38,7 @@ The Laravel source code is managed on Github, and there are repositories for eac
You may propose new features or improvements of existing Laravel behavior in the Laravel Internals [issue board](https://github.com/laravel/internals/issues). If you propose a new feature, please be willing to implement at least some of the code that would be needed to complete the feature.
-Informal discussion regarding bugs, new features, and implementation of existing features takes place in the `#internals` channel of the [LaraChat](http://larachat.co) Slack team. Taylor Otwell, the maintainer of Laravel, is typically present in the channel on weekdays from 8am-5pm (UTC-06:00 or America/Chicago), and sporadically present in the channel at other times.
+Informal discussion regarding bugs, new features, and implementation of existing features takes place in the `#internals` channel of the [LaraChat](https://larachat.co) Slack team. Taylor Otwell, the maintainer of Laravel, is typically present in the channel on weekdays from 8am-5pm (UTC-06:00 or America/Chicago), and sporadically present in the channel at other times.
## Which Branch?
@@ -46,7 +49,7 @@ Informal discussion regarding bugs, new features, and implementation of existing
**Major** new features should always be sent to the `master` branch, which contains the upcoming Laravel release.
-If you are unsure if your feature qualifies as a major or minor, please ask Taylor Otwell in the `#internals` channel of the [LaraChat](http://larachat.co) Slack team.
+If you are unsure if your feature qualifies as a major or minor, please ask Taylor Otwell in the `#internals` channel of the [LaraChat](https://larachat.co) Slack team.
## Security Vulnerabilities
diff --git a/controllers.md b/controllers.md
index 402f2fb..307df0d 100644
--- a/controllers.md
+++ b/controllers.md
@@ -107,7 +107,7 @@ However, it is more convenient to specify middleware within your controller's co
class UserController extends Controller
{
/**
- * Instantiate a new new controller instance.
+ * Instantiate a new controller instance.
*
* @return void
*/
@@ -121,6 +121,14 @@ However, it is more convenient to specify middleware within your controller's co
}
}
+Controllers also allow you to register middleware using a Closure. This provides a convenient way to define a middleware for a single controller without defining an entire middleware class:
+
+ $this->middleware(function ($request, $next) {
+ // ...
+
+ return $next($request);
+ });
+
> {tip} You may assign middleware to a subset of controller actions; however, it may indicate your controller is growing too large. Instead, consider breaking your controller into multiple, smaller controllers.
@@ -150,6 +158,12 @@ GET | `/photos/{photo}/edit` | edit | photos.edit
PUT/PATCH | `/photos/{photo}` | update | photos.update
DELETE | `/photos/{photo}` | destroy | photos.destroy
+#### Specifying The Resource Model
+
+If you are using route model binding and would like the resource controller's methods to type-hint a model instance, you may use the `--model` option when generating the controller:
+
+ php artisan make:controller PhotoController --resource --model=Photo
+
#### Spoofing Form Methods
Since HTML forms can't make `PUT`, `PATCH`, or `DELETE` requests, you will need to add a hidden `_method` field to spoof these HTTP verbs. The `method_field` helper can create this field for you:
diff --git a/csrf.md b/csrf.md
index 8f67e03..7533ffc 100644
--- a/csrf.md
+++ b/csrf.md
@@ -8,7 +8,7 @@
## Introduction
-Laravel makes it easy to protect your application from [cross-site request forgery](http://en.wikipedia.org/wiki/Cross-site_request_forgery) (CSRF) attacks. Cross-site request forgeries are a type of malicious exploit whereby unauthorized commands are performed on behalf of an authenticated user.
+Laravel makes it easy to protect your application from [cross-site request forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) (CSRF) attacks. Cross-site request forgeries are a type of malicious exploit whereby unauthorized commands are performed on behalf of an authenticated user.
Laravel automatically generates a CSRF "token" for each active user session managed by the application. This token is used to verify that the authenticated user is the one actually making the requests to the application.
diff --git a/database-testing.md b/database-testing.md
index 27898fa..36f10e8 100644
--- a/database-testing.md
+++ b/database-testing.md
@@ -5,7 +5,7 @@
- [Using Migrations](#using-migrations)
- [Using Transactions](#using-transactions)
- [Writing Factories](#writing-factories)
- - [Factory Types](#factory-types)
+ - [Factory States](#factory-states)
- [Using Factories](#using-factories)
- [Creating Models](#creating-models)
- [Persisting Models](#persisting-models)
@@ -14,18 +14,18 @@
## Introduction
-Laravel provides a variety of helpful tools to make it easier to test your database driven applications. First, you may use the `seeInDatabase` helper to assert that data exists in the database matching a given set of criteria. For example, if you would like to verify that there is a record in the `users` table with the `email` value of `sally@example.com`, you can do the following:
+Laravel provides a variety of helpful tools to make it easier to test your database driven applications. First, you may use the `assertDatabaseHas` helper to assert that data exists in the database matching a given set of criteria. For example, if you would like to verify that there is a record in the `users` table with the `email` value of `sally@example.com`, you can do the following:
public function testDatabase()
{
// Make call to application...
- $this->seeInDatabase('users', [
+ $this->assertDatabaseHas('users', [
'email' => 'sally@example.com'
]);
}
-Of course, the `seeInDatabase` method and other helpers like it are for convenience. You are free to use any of PHPUnit's built-in assertion methods to supplement your tests.
+Of course, the `assertDatabaseHas` method and other helpers like it are for convenience. You are free to use any of PHPUnit's built-in assertion methods to supplement your tests.
## Resetting The Database After Each Test
@@ -39,6 +39,9 @@ One approach to resetting the database state is to rollback the database after e
visit('/')
- ->see('Laravel 5');
+ $response = $this->get('/');
+
+ // ...
}
}
@@ -66,6 +70,9 @@ Another approach to resetting the database state is to wrap each test case in a
visit('/')
- ->see('Laravel 5');
+ $response = $this->get('/');
+
+ // ...
}
}
-> {note} This trait will only wrap the default database connection in a transaction. If your application is using multiple database connections, you will need to manually handle the transaction logic for those connections.
+> {note} By default, this trait will only wrap the default database connection in a transaction. If your application is using multiple database connections, you should define a `$connectionsToTransact` property on your test class. This property should be an array of connection names to execute the transactions on.
-
+
## Writing Factories
-When testing, it is common to need to insert a few records into your database before executing your test. Instead of manually specifying the value of each column when you create this test data, Laravel allows you to define a default set of attributes for each of your [Eloquent models](/docs/{{version}}/eloquent) using model factories. To get started, take a look at the `database/factories/ModelFactory.php` file in your application. Out of the box, this file contains one factory definition:
+When testing, you may need to insert a few records into your database before executing your test. Instead of manually specifying the value of each column when you create this test data, Laravel allows you to define a default set of attributes for each of your [Eloquent models](/docs/{{version}}/eloquent) using model factories. To get started, take a look at the `database/factories/ModelFactory.php` file in your application. Out of the box, this file contains one factory definition:
$factory->define(App\User::class, function (Faker\Generator $faker) {
+ static $password;
+
return [
'name' => $faker->name,
- 'email' => $faker->email,
- 'password' => bcrypt(str_random(10)),
+ 'email' => $faker->unique()->safeEmail,
+ 'password' => $password ?: $password = bcrypt('secret'),
'remember_token' => str_random(10),
];
});
@@ -106,29 +116,17 @@ Within the Closure, which serves as the factory definition, you may return the d
Of course, you are free to add your own additional factories to the `ModelFactory.php` file. You may also create additional factory files for each model for better organization. For example, you could create `UserFactory.php` and `CommentFactory.php` files within your `database/factories` directory. All of the files within the `factories` directory will automatically be loaded by Laravel.
-
-### Factory Types
+
+### Factory States
-Sometimes you may wish to have multiple factories for the same Eloquent model class. For example, perhaps you would like to have a factory for "Administrator" users in addition to normal users. You may define these factories using the `defineAs` method:
+States allow you to define discrete modifications that can be applied to your model factories in any combination. For example, your `User` model might have a `delinquent` state that modifies one of its default attribute values. You may define your state transformations using the `state` method:
- $factory->defineAs(App\User::class, 'admin', function ($faker) {
+ $factory->state(App\User::class, 'delinquent', function ($faker) {
return [
- 'name' => $faker->name,
- 'email' => $faker->email,
- 'password' => str_random(10),
- 'remember_token' => str_random(10),
- 'admin' => true,
+ 'account_status' => 'delinquent',
];
});
-Instead of duplicating all of the attributes from your base user factory, you may use the `raw` method to retrieve the base attributes. Once you have the attributes, simply supplement them with any additional values you require:
-
- $factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) {
- $user = $factory->raw(App\User::class);
-
- return array_merge($user, ['admin' => true]);
- });
-
## Using Factories
@@ -149,11 +147,13 @@ You may also create a Collection of many models or create models of a given type
// Create three App\User instances...
$users = factory(App\User::class, 3)->make();
- // Create an "admin" App\User instance...
- $user = factory(App\User::class, 'admin')->make();
+#### Applying States
+
+You may also apply any of your [states](#factory-states) to the models. If you would like to apply multiple state transformations to the models, you should specify the name of each state you would like to apply:
+
+ $users = factory(App\User::class, 5)->states('delinquent')->make();
- // Create three "admin" App\User instances...
- $users = factory(App\User::class, 'admin', 3)->make();
+ $users = factory(App\User::class, 5)->states('premium', 'delinquent')->make();
#### Overriding Attributes
@@ -210,7 +210,7 @@ You may also attach relationships to models using Closure attributes in your fac
];
});
-These Closures also receive the evaluated attribute array of the factory that contains them:
+These Closures also receive the evaluated attribute array of the factory that defines them:
$factory->define(App\Post::class, function ($faker) {
return [
diff --git a/database.md b/database.md
index ad14737..3090a0f 100644
--- a/database.md
+++ b/database.md
@@ -7,7 +7,6 @@
- [Running Raw SQL Queries](#running-queries)
- [Listening For Query Events](#listening-for-query-events)
- [Database Transactions](#database-transactions)
-- [Using Multiple Database Connections](#accessing-connections)
## Introduction
@@ -26,7 +25,7 @@ Laravel makes interacting with databases extremely simple across a variety of da
The database configuration for your application is located at `config/database.php`. In this file you may define all of your database connections, as well as specify which connection should be used by default. Examples for most of the supported database systems are provided in this file.
-By default, Laravel's sample [environment configuration](/docs/{{version}}/installation#environment-configuration) is ready to use with [Laravel Homestead](/docs/{{version}}/homestead), which is a convenient virtual machine for doing Laravel development on your local machine. Of course, you are free to modify this configuration as needed for your local database.
+By default, Laravel's sample [environment configuration](/docs/{{version}}/configuration#environment-configuration) is ready to use with [Laravel Homestead](/docs/{{version}}/homestead), which is a convenient virtual machine for doing Laravel development on your local machine. Of course, you are free to modify this configuration as needed for your local database.
#### SQLite Configuration
@@ -206,6 +205,16 @@ You may use the `transaction` method on the `DB` facade to run a set of operatio
DB::table('posts')->delete();
});
+#### Handling Deadlocks
+
+The `transaction` method accepts an optional second argument which defines the number of times a transaction should be reattempted when a deadlock occurs. Once these attempts have been exhausted, an exception will be thrown:
+
+ DB::transaction(function () {
+ DB::table('users')->update(['votes' => 1]);
+
+ DB::table('posts')->delete();
+ }, 5);
+
#### Manually Using Transactions
If you would like to begin a transaction manually and have complete control over rollbacks and commits, you may use the `beginTransaction` method on the `DB` facade:
diff --git a/documentation.md b/documentation.md
index 328d7ef..ab11db8 100644
--- a/documentation.md
+++ b/documentation.md
@@ -7,7 +7,7 @@
- [Installation](/docs/{{version}}/installation)
- [Configuration](/docs/{{version}}/configuration)
- [Directory Structure](/docs/{{version}}/structure)
- - [Errors & Logging](/docs/{{version}}/errors)
+ - [Request Lifecycle](/docs/{{version}}/lifecycle)
- Dev Environments
- [Homestead](/docs/{{version}}/homestead)
- [Valet](/docs/{{version}}/valet)
@@ -23,29 +23,35 @@
- [Controllers](/docs/{{version}}/controllers)
- [Requests](/docs/{{version}}/requests)
- [Responses](/docs/{{version}}/responses)
+ - [Views](/docs/{{version}}/views)
- [Session](/docs/{{version}}/session)
- [Validation](/docs/{{version}}/validation)
- Frontend
- - [Views](/docs/{{version}}/views)
- [Blade Templates](/docs/{{version}}/blade)
- - [Compiling Assets](/docs/{{version}}/elixir)
- [Localization](/docs/{{version}}/localization)
+ - [Frontend Scaffolding](/docs/{{version}}/frontend)
+ - [Compiling Assets](/docs/{{version}}/mix)
- Security
- [Authentication](/docs/{{version}}/authentication)
+ - [API Authentication](/docs/{{version}}/passport)
- [Authorization](/docs/{{version}}/authorization)
- - [Password Reset](/docs/{{version}}/passwords)
- [Encryption](/docs/{{version}}/encryption)
- [Hashing](/docs/{{version}}/hashing)
-- API Development
- - [Authentication](/docs/{{version}}/api-authentication)
+ - [Password Reset](/docs/{{version}}/passwords)
- General Topics
+ - [Artisan Console](/docs/{{version}}/artisan)
- [Broadcasting](/docs/{{version}}/broadcasting)
- [Cache](/docs/{{version}}/cache)
+ - [Collections](/docs/{{version}}/collections)
+ - [Errors & Logging](/docs/{{version}}/errors)
- [Events](/docs/{{version}}/events)
- [File Storage](/docs/{{version}}/filesystem)
+ - [Helpers](/docs/{{version}}/helpers)
- [Mail](/docs/{{version}}/mail)
- [Notifications](/docs/{{version}}/notifications)
+ - [Packages](/docs/{{version}}/packages)
- [Queues](/docs/{{version}}/queues)
+ - [Scheduled Tasks](/docs/{{version}}/scheduling)
- Database
- [Getting Started](/docs/{{version}}/database)
- [Query Builder](/docs/{{version}}/queries)
@@ -59,20 +65,15 @@
- [Collections](/docs/{{version}}/eloquent-collections)
- [Mutators](/docs/{{version}}/eloquent-mutators)
- [Serialization](/docs/{{version}}/eloquent-serialization)
-- Artisan Console
- - [Commands](/docs/{{version}}/artisan)
- - [Task Scheduling](/docs/{{version}}/scheduling)
- Testing
- [Getting Started](/docs/{{version}}/testing)
- - [Application Testing](/docs/{{version}}/application-testing)
+ - [HTTP Tests](/docs/{{version}}/http-tests)
+ - [Browser Tests](/docs/{{version}}/dusk)
- [Database](/docs/{{version}}/database-testing)
- [Mocking](/docs/{{version}}/mocking)
- Official Packages
- [Cashier](/docs/{{version}}/billing)
- [Envoy](/docs/{{version}}/envoy)
+ - [Passport](/docs/{{version}}/passport)
- [Scout](/docs/{{version}}/scout)
- [Socialite](https://github.com/laravel/socialite)
-- Appendix
- - [Collections](/docs/{{version}}/collections)
- - [Helpers](/docs/{{version}}/helpers)
- - [Packages](/docs/{{version}}/packages)
diff --git a/dusk.md b/dusk.md
new file mode 100644
index 0000000..1010224
--- /dev/null
+++ b/dusk.md
@@ -0,0 +1,584 @@
+# Browser Tests (Laravel Dusk)
+
+- [Introduction](#introduction)
+- [Installation](#installation)
+ - [Using Other Browsers](#using-other-browsers)
+- [Getting Started](#getting-started)
+ - [Generating Tests](#generating-tests)
+ - [Running Tests](#running-tests)
+ - [Environment Handling](#environment-handling)
+ - [Creating Browsers](#creating-browsers)
+ - [Authentication](#authentication)
+- [Interacting With Elements](#interacting-with-elements)
+ - [Clicking Links](#clicking-links)
+ - [Text, Values, & Attributes](#text-values-and-attributes)
+ - [Using Forms](#using-forms)
+ - [Attaching Files](#attaching-files)
+ - [Using The Keyboard](#using-the-keyboard)
+ - [Using The Mouse](#using-the-mouse)
+ - [Scoping Selectors](#scoping-selectors)
+ - [Waiting For Elements](#waiting-for-elements)
+- [Available Assertions](#available-assertions)
+- [Pages](#pages)
+ - [Generating Pages](#generating-pages)
+ - [Configuring Pages](#configuring-pages)
+ - [Navigating To Pages](#navigating-to-pages)
+ - [Shorthand Selectors](#shorthand-selectors)
+ - [Page Methods](#page-methods)
+
+
+## Introduction
+
+Laravel Dusk provides an expressive, easy-to-use browser automation and testing API. By default, Dusk does not require you to install JDK or Selenium on your machine. Instead, Dusk uses a standalone [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/home) installation. However, you are free to utilize any other Selenium compatible driver you wish.
+
+
+## Installation
+
+To get started, you should add the `laravel/dusk` Composer dependency to your project:
+
+ composer require laravel/dusk
+
+Once Dusk is installed, you should register the `Laravel\Dusk\DuskServiceProvider` service provider. You should register the provider within the `register` method of your `AppServiceProvider` in order to limit the environments in which Dusk is available, since it exposes the ability to login as other users:
+
+ use Laravel\Dusk\DuskServiceProvider;
+
+ /**
+ * Register any application services.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ if ($this->app->environment('local', 'testing')) {
+ $this->app->register(DuskServiceProvider::class);
+ }
+ }
+
+Next, run the `dusk:install` Artisan command:
+
+ php artisan dusk:install
+
+A `Browser` directory will be created within your `tests` directory and will contain an example test. Next, set the `APP_URL` environment variable in your `.env` file. This value should match the URL you use to access your application in a browser.
+
+To run your tests, use the `dusk` Artisan command. The `dusk` command accepts any argument that is also accepted by the `phpunit` command:
+
+ php artisan dusk
+
+
+### Using Other Browsers
+
+By default, Dusk uses Google Chrome and a standalone [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/home) installation to run your browser tests. However, you may start your own Selenium server and run your tests against any browser you wish.
+
+To get started, open your `tests/DuskTestCase.php` file, which is the base Dusk test case for your application. Within this file, you can remove the call to the `startChromeDriver` method. This will stop Dusk from automatically starting the ChromeDriver:
+
+ /**
+ * Prepare for Dusk test execution.
+ *
+ * @beforeClass
+ * @return void
+ */
+ public static function prepare()
+ {
+ // static::startChromeDriver();
+ }
+
+Next, you may simply modify the `driver` method to connect to the URL and port of your choice. In addition, you may modify the "desired capabilities" that should be passed to the WebDriver:
+
+ /**
+ * Create the RemoteWebDriver instance.
+ *
+ * @return \Facebook\WebDriver\Remote\RemoteWebDriver
+ */
+ protected function driver()
+ {
+ return RemoteWebDriver::create(
+ 'http://localhost:4444', DesiredCapabilities::phantomjs()
+ );
+ }
+
+
+## Getting Started
+
+
+### Generating Tests
+
+To generate a Dusk test, use the `dusk:make` Artisan command. The generated test will be placed in the `tests/Browser` directory:
+
+ php artisan dusk:make LoginTest
+
+
+### Running Tests
+
+To run your browser tests, use the `dusk` Artisan command:
+
+ php artisan dusk
+
+The `dusk` command accepts any argument that is normally accepted by the PHPUnit test runner, allowing you to only run the tests for a given [group](https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.group), etc:
+
+ php artisan dusk --group=foo
+
+#### Manually Starting ChromeDriver
+
+By default, Dusk will automatically attempt to start ChromeDriver. If this does not work for your particular system, you may manually start ChromeDriver before running the `dusk` command. If you choose to start ChromeDriver manually, you should comment out the following line of your `tests/DuskTestCase.php` file:
+
+ /**
+ * Prepare for Dusk test execution.
+ *
+ * @beforeClass
+ * @return void
+ */
+ public static function prepare()
+ {
+ // static::startChromeDriver();
+ }
+
+In addition, if you start ChromeDriver on a port other than 9515, you should modify the `driver` method of the same class:
+
+ /**
+ * Create the RemoteWebDriver instance.
+ *
+ * @return \Facebook\WebDriver\Remote\RemoteWebDriver
+ */
+ protected function driver()
+ {
+ return RemoteWebDriver::create(
+ 'http://localhost:9515', DesiredCapabilities::chrome()
+ );
+ }
+
+
+### Environment Handling
+
+To force Dusk to use its own environment file when running tests, create a `.env.dusk.{environment}` file in the root of your project. For example, if you will be initiating the `dusk` command from your `local` environment, you should create a `.env.dusk.local` file.
+
+When running tests, Dusk will back-up your `.env` file and rename your Dusk environment to `.env`. Once the tests have completed, your `.env` file will be restored.
+
+
+### Creating Browsers
+
+To get started, let's write a test that verifies we can log into our application. After generating a test, we can modify it to navigate to the login page, enter some credentials, and click the "Login" button. To create a browser instance, call the `browse` method:
+
+ create([
+ 'email' => 'taylor@laravel.com',
+ ]);
+
+ $this->browse(function ($browser) use ($user) {
+ $browser->visit('/login')
+ ->type('email', $user->email)
+ ->type('password', 'secret')
+ ->press('Login')
+ ->assertPathIs('/home');
+ });
+ }
+ }
+
+As you can see in the example above, the `browse` method accepts a callback. A browser instance will automatically be passed to this callback by Dusk and is the main object used to interact with and make assertions against your application.
+
+> {tip} This test can be used to test the login screen generated by the `make:auth` Artisan command.
+
+#### Creating Multiple Browsers
+
+Sometimes you may need multiple browsers in order to properly carry out a test. For example, multiple browsers may be needed to test a chat screen that interacts with websockets. To create multiple browsers, simply "ask" for more than one browser in the signature of the callback given to the `browse` method:
+
+ $this->browse(function ($first, $second) {
+ $first->loginAs(User::find(1))
+ ->visit('/home')
+ ->waitForText('Message');
+
+ $second->loginAs(User::find(2))
+ ->visit('/home')
+ ->waitForText('Message')
+ ->type('message', 'Hey Taylor')
+ ->press('Send');
+
+ $first->waitForText('Hey Taylor')
+ ->assertSee('Jeffrey Way');
+ });
+
+
+### Authentication
+
+Often, you will be testing pages that require authentication. You can use Dusk's `loginAs` method in order to avoid interacting with the login screen during every test. The `loginAs` method accepts a user ID or user model instance:
+
+ $this->browse(function ($first, $second) {
+ $first->loginAs(User::find(1))
+ ->visit('/home');
+ });
+
+
+## Interacting With Elements
+
+
+### Clicking Links
+
+To click a link, you may use the `clickLink` method on the browser instance. The `clickLink` method will click the link that has the given display text:
+
+ $browser->clickLink($linkText);
+
+> {note} This method interacts 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.
+
+
+### Text, Values, & Attributes
+
+#### Retrieving & Setting Values
+
+Dusk provides several methods for interacting with the current display text, value, and attributes of elements on the page. For example, to get the "value" of an element that matches a given selector, use the `value` method:
+
+ // Retrieve the value...
+ $value = $browser->value('selector');
+
+ // Set the value...
+ $browser->value('selector', 'value');
+
+#### Retrieving Text
+
+The `text` method may be used to retrieve the display text of an element that matches the given selector:
+
+ $text = $browser->text('selector');
+
+#### Retrieving Attributes
+
+Finally, the `attribute` method may be used to retrieve an attribute of an element matching the given selector:
+
+ $attribute = $browser->attribute('selector', 'value');
+
+
+### Using Forms
+
+#### Typing Values
+
+Dusk provides a variety of methods for interacting with forms and input elements. First, let's take a look at an example of typing text into an input field:
+
+ $browser->type('email', 'taylor@laravel.com');
+
+Note that, although the method accepts one if necessary, we are not required to pass a CSS selector into the `type` method. If a CSS selector is not provided, Dusk will search for an input field with the given `name` attribute. Finally, Dusk will attempt to find a `textarea` with the given `name` attribute.
+
+You may "clear" the value of an input using the `clear` method:
+
+ $browser->clear('email');
+
+#### Dropdowns
+
+To select a value in a dropdown selection box, you may use the `select` method. Like the `type` method, the `select` method does not require a full CSS selector. When passing a value to the `select` method, you should pass the underlying option value instead of the display text:
+
+ $browser->select('size', 'Large');
+
+#### Checkboxes
+
+To "check" a checkbox field, you may use the `check` method. Like many other input related methods, a full CSS selector is not required. If an exact selector match can't be found, Dusk will search for a checkbox with a matching `name` attribute:
+
+ $browser->check('terms');
+
+ $browser->uncheck('terms');
+
+#### Radio Buttons
+
+To "select" a radio button option, you may use the `radio` method. Like many other input related methods, a full CSS selector is not required. If an exact selector match can't be found, Dusk will search for a radio with matching `name` and `value` attributes:
+
+ $browser->radio('version', 'php7');
+
+
+### Attaching Files
+
+The `attach` method may be used to attach a file to a `file` input element. Like many other input related methods, a full CSS selector is not required. If an exact selector match can't be found, Dusk will search for a file input with matching `name` attribute:
+
+ $browser->attach('photo', __DIR__.'/photos/me.png');
+
+
+### Using The Keyboard
+
+The `keys` method allows you to provide more complex input sequences to a given element than normally allowed by the `type` method. For example, you may hold modifier keys entering values. In this example, the `shift` key will be held while `taylor` is entered into the element matching the given selector. After `taylor` is typed, `otwell` will be typed without any modifier keys:
+
+ $browser->keys('selector', ['{shift}', 'taylor'], 'otwell');
+
+You may even send a "hot key" to the primary CSS selector that contains your application:
+
+ $browser->keys('.app', ['{command}', 'j']);
+
+> {tip} All modifier keys are wrapped in `{}` characters, and match the constants defined in the `Facebook\WebDriver\WebDriverKeys` class, which can be [found on GitHub](https://github.com/facebook/php-webdriver/blob/community/lib/WebDriverKeys.php).
+
+
+### Using The Mouse
+
+#### Clicking On Elements
+
+The `click` method may be used to "click" on an element matching the given selector:
+
+ $browser->click('.selector');
+
+#### Mouseover
+
+The `mouseover` method may be used when you need to move the mouse over an element matching the given selector:
+
+ $browser->mouseover('.selector');
+
+#### Drag & Drop
+
+The `drag` method may be used to drag an element matching the given selector to another element:
+
+ $browser->drag('.from-selector', '.to-selector');
+
+
+### Scoping Selectors
+
+Sometimes you may wish to perform several operations while scoping all of the operations within a given selector. For example, you may wish to assert that some text exists only within a table and then click a button within that table. You may use the `with` method to accomplish this. All operations performed within the callback given to the `with` method will be scoped to the original selector:
+
+ $browser->with('.table', function ($table) {
+ $table->assertSee('Hello World')
+ ->clickLink('Delete');
+ });
+
+
+### Waiting For Elements
+
+When testing applications that use JavaScript extensively, it often becomes necessary to "wait" for certain elements or data to be available before proceeding with a test. Dusk makes this a cinch. Using a variety of methods, you may wait for elements to be visible on the page or even wait until a given JavaScript expression evaluates to `true`.
+
+#### Waiting
+
+If you need to pause the test for a given number of milliseconds, use the `pause` method:
+
+ $browser->pause(1000);
+
+#### Waiting For Selectors
+
+The `waitFor` method may be used to pause the execution of the test until the element matching the given CSS selector is displayed on the page. By default, this will pause the test for a maximum of five seconds before throwing an exception. If necessary, you may pass a custom timeout threshold as the second argument to the method:
+
+ // Wait a maximum of five seconds for the selector...
+ $browser->waitFor('.selector');
+
+ // Wait a maximum of one second for the selector...
+ $browser->waitFor('.selector', 1);
+
+You may also wait until the given selector is missing from the page:
+
+ $browser->waitUntilMissing('.selector');
+
+ $browser->waitUntilMissing('.selector', 1);
+
+#### Scoping Selectors When Available
+
+Occasionally, you may wish to wait for a given selector and then interact with the element matching the selector. For example, you may wish to wait until a modal window is available and then press the "OK" button within the modal. The `whenAvailable` method may be used in this case. All element operations performed within the given callback will be scoped to the original selector:
+
+ $browser->whenAvailable('.modal', function ($modal) {
+ $modal->assertSee('Hello World')
+ ->press('OK');
+ });
+
+#### Waiting For Text
+
+The `waitForText` method may be used to wait until the given text is displayed on the page:
+
+ // Wait a maximum of five seconds for the text...
+ $browser->waitForText('Hello World');
+
+ // Wait a maximum of one second for the text...
+ $browser->waitForText('Hello World', 1);
+
+#### Waiting For Links
+
+The `waitForLink` method may be used to wait until the given link text is displayed on the page:
+
+ // Wait a maximum of five seconds for the link...
+ $browser->waitForLink('Create');
+
+ // Wait a maximum of one second for the link...
+ $browser->waitForLink('Create', 1);
+
+#### Waiting On JavaScript Expressions
+
+Sometimes you may wish to pause the execution of a test until a given JavaScript expression evaluates to `true`. You may easily accomplish this using the `waitUntil` method. When passing an expression to this method, you do not need to include the `return` keyword or an ending semi-colon:
+
+ // Wait a maximum of five seconds for the expression to be true...
+ $browser->waitUntil('App.dataLoaded');
+
+ $browser->waitUntil('App.data.servers.length > 0');
+
+ // Wait a maximum of one second for the expression to be true...
+ $browser->waitUntil('App.data.servers.length > 0', 1);
+
+
+## Available Assertions
+
+Dusk provides a variety of assertions that you may make against your application. All of the available assertions are documented in the table below:
+
+Assertion | Description
+------------- | -------------
+`$browser->assertTitle($title)` | Assert the page title matches the given text.
+`$browser->assertTitleContains($title)` | Assert the page title contains the given text.
+`$browser->assertPathIs('/home')` | Assert the current path matches the given path.
+`$browser->assertHasCookie($name)` | Assert the given cookie is present.
+`$browser->assertCookieValue($name, $value)` | Assert a cookie has a given value.
+`$browser->assertPlainCookieValue($name, $value)` | Assert an unencrypted cookie has a given value.
+`$browser->assertSee($text)` | Assert the given text is present on the page.
+`$browser->assertDontSee($text)` | Assert the given text is not present on the page.
+`$browser->assertSeeIn($selector, $text)` | Assert the given text is present within the selector.
+`$browser->assertDontSeeIn($selector, $text)` | Assert the given text is not present within the selector.
+`$browser->assertSeeLink($linkText)` | Assert the given link is present on the page.
+`$browser->assertDontSeeLink($linkText)` | Assert the given link is not present on the page.
+`$browser->assertInputValue($field, $value)` | Assert the given input field has the given value.
+`$browser->assertInputValueIsNot($field, $value)` | Assert the given input field does not have the given value.
+`$browser->assertChecked($field)` | Assert the given checkbox is checked.
+`$browser->assertNotChecked($field)` | Assert the given checkbox is not checked.
+`$browser->assertSelected($field, $value)` | Assert the given dropdown has the given value selected.
+`$browser->assertNotSelected($field, $value)` | Assert the given dropdown does not have the given value selected.
+`$browser->assertValue($selector, $value)` | Assert the element matching the given selector has the given value.
+`$browser->assertVisible($selector)` | Assert the element matching the given selector is visible.
+`$browser->assertMissing($selector)` | Assert the element matching the given selector is not visible.
+
+
+## Pages
+
+Sometimes, tests require several complicated actions to be performed in sequence. This can make your tests harder to read and understand. Pages allow you to define expressive actions that may then be performed on a given page using a single method. Pages also allow you to define short-cuts to common selectors for your application or a single page.
+
+
+### Generating Pages
+
+To generate a page object, use the `dusk:page` Artisan command. All page objects will be placed in the `tests/Browser/Pages` directory:
+
+ php artisan dusk:page Login
+
+
+### Configuring Pages
+
+By default, pages have three methods: `url`, `assert`, and `selectors`. We will discuss the `url` and `assert` methods now. The `selectors` method will be [discussed in more detail below](#shorthand-selectors).
+
+#### The `url` Method
+
+The `url` method should return the path of the URL that represents the page. Dusk will use this URL when navigating to the page in the browser:
+
+ /**
+ * Get the URL for the page.
+ *
+ * @return string
+ */
+ public function url()
+ {
+ return '/login';
+ }
+
+#### The `assert` Method
+
+The `assert` method may make any assertions necessary to verify that the browser is actually on the given page. Completing this method is not necessary; however, you are free to make these assertions if you wish. These assertions will be run automatically when navigating to the page:
+
+ /**
+ * Assert that the browser is on the page.
+ *
+ * @return void
+ */
+ public function assert(Browser $browser)
+ {
+ $browser->assertPathIs($this->url());
+ }
+
+
+### Navigating To Pages
+
+Once a page has been configured, you may navigate to it using the `visit` method:
+
+ use Tests\Browser\Pages\Login;
+
+ $browser->visit(new Login);
+
+Sometimes you may already be on a given page and need to "load" the page's selectors and methods into the current test context. This is common when pressing a button and being redirected to a given page without explicitly navigating to it. In this situation, you may use the `on` method to load the page:
+
+ use Tests\Browser\Pages\CreatePlaylist;
+
+ $browser->visit('/dashboard')
+ ->clickLink('Create Playlist')
+ ->on(new CreatePlaylist)
+ ->assertSee('@create');
+
+
+### Shorthand Selectors
+
+The `selectors` method of pages allows you to define quick, easy-to-remember shortcuts for any CSS selector on your page. For example, let's define a shortcut for the "email" input field of the application's login page:
+
+ /**
+ * Get the element shortcuts for the page.
+ *
+ * @return array
+ */
+ public function elements()
+ {
+ return [
+ '@email' => 'input[name=email]',
+ ];
+ }
+
+Now, you may use this shorthand selector anywhere you would use a full CSS selector:
+
+ $browser->type('@email', 'taylor@laravel.com');
+
+#### Global Shorthand Selectors
+
+After installing Dusk, a base `Page` class will be placed in your `tests/Browser/Pages` directory. This class contains a `siteElements` method which may be used to define global shorthand selectors that should be available on every page throughout your application:
+
+ /**
+ * Get the global element shortcuts for the site.
+ *
+ * @return array
+ */
+ public static function siteElements()
+ {
+ return [
+ '@element' => '#selector',
+ ];
+ }
+
+
+### Page Methods
+
+In addition to the default methods defined on pages, you may define additional methods which may be used throughout your tests. For example, let's imagine we are building a music management application. A common action for one page of the application might be to create a playlist. Instead of re-writing the logic to create a playlist in each test, you may define a `createPlaylist` method on a page class:
+
+ type('name', $name)
+ ->check('share')
+ ->press('Create Playlist');
+ }
+ }
+
+Once the method has been defined, you may use it within any test that utilizes the page. The browser instance will automatically be passed to the page method:
+
+ use Tests\Browser\Pages\Dashboard;
+
+ $browser->visit(new Dashboard)
+ ->createPlaylist('My Playlist')
+ ->assertSee('My Playlist');
diff --git a/elixir.md b/elixir.md
deleted file mode 100644
index cb80437..0000000
--- a/elixir.md
+++ /dev/null
@@ -1,351 +0,0 @@
-# Compiling Assets (Laravel Elixir)
-
-- [Introduction](#introduction)
-- [Installation & Setup](#installation)
-- [Running Elixir](#running-elixir)
-- [Working With Stylesheets](#working-with-stylesheets)
- - [Less](#less)
- - [Sass](#sass)
- - [Stylus](#stylus)
- - [Plain CSS](#plain-css)
- - [Source Maps](#css-source-maps)
-- [Working With Scripts](#working-with-scripts)
- - [Webpack](#webpack)
- - [Rollup](#rollup)
- - [Scripts](#javascript)
-- [Copying Files & Directories](#copying-files-and-directories)
-- [Versioning / Cache Busting](#versioning-and-cache-busting)
-- [BrowserSync](#browser-sync)
-
-
-## Introduction
-
-Laravel Elixir provides a clean, fluent API for defining basic [Gulp](http://gulpjs.com) tasks for your Laravel application. Elixir supports common CSS and JavaScript pre-processors like [Sass](http://sass-lang.com) and [Webpack](https://webpack.github.io/). Using method chaining, Elixir allows you to fluently define your asset pipeline. For example:
-
-```javascript
-elixir(function(mix) {
- mix.sass('app.scss')
- .webpack('app.js');
-});
-```
-
-If you've ever been confused and overwhelmed about getting started with Gulp and asset compilation, you will love Laravel Elixir. However, you are not required to use it while developing your application. You are free to use any asset pipeline tool you wish, or even none at all.
-
-
-## Installation & Setup
-
-#### Installing Node
-
-Before triggering Elixir, you must first ensure that Node.js and NPM are installed on your machine.
-
- node -v
- npm -v
-
-By default, Laravel Homestead includes everything you need; however, if you aren't using Vagrant, then you can easily install the latest version of Node and NPM using simple graphical installers from [their download page](http://nodejs.org/en/download/).
-
-#### Gulp
-
-Next, you'll need to pull in [Gulp](http://gulpjs.com) as a global NPM package:
-
- npm install --global gulp-cli
-
-#### Laravel Elixir
-
-The only remaining step is to install Laravel Elixir. Within a fresh installation of Laravel, you'll find a `package.json` file in the root of your directory structure. The default `package.json` file includes Elixir and the Webpack JavaScript module bundler. Think of this like your `composer.json` file, except it defines Node dependencies instead of PHP. You may install the dependencies it references by running:
-
- npm install
-
-If you are developing on a Windows system or you are running your VM on a Windows host system, you may need to run the `npm install` command with the `--no-bin-links` switch enabled:
-
- npm install --no-bin-links
-
-
-## Running Elixir
-
-Elixir is built on top of [Gulp](http://gulpjs.com), so to run your Elixir tasks you only need to run the `gulp` command in your terminal. Adding the `--production` flag to the command will instruct Elixir to minify your CSS and JavaScript files:
-
- // Run all tasks...
- gulp
-
- // Run all tasks and minify all CSS and JavaScript...
- gulp --production
-
-Upon running this command, you'll see a nicely formatted table that displays a summary of the events that just took place.
-
-#### Watching Assets For Changes
-
-The `gulp watch` command will continue running in your terminal and watch your assets for any changes. Gulp will automatically recompile your assets if you modify them while the `watch` command is running:
-
- gulp watch
-
-
-## Working With Stylesheets
-
-The `gulpfile.js` file in your project's root directory contains all of your Elixir tasks. Elixir tasks can be chained together to define exactly how your assets should be compiled.
-
-
-### Less
-
-The `less` method may be used to compile [Less](http://lesscss.org/) into CSS. The `less` method assumes that your Less files are stored in `resources/assets/less`. By default, the task will place the compiled CSS for this example in `public/css/app.css`:
-
-```javascript
-elixir(function(mix) {
- mix.less('app.less');
-});
-```
-
-You may also combine multiple Less files into a single CSS file. Again, the resulting CSS will be placed in `public/css/app.css`:
-
-```javascript
-elixir(function(mix) {
- mix.less([
- 'app.less',
- 'controllers.less'
- ]);
-});
-```
-
-If you wish to customize the output location of the compiled CSS, you may pass a second argument to the `less` method:
-
-```javascript
-elixir(function(mix) {
- mix.less('app.less', 'public/stylesheets');
-});
-
-// Specifying a specific output filename...
-elixir(function(mix) {
- mix.less('app.less', 'public/stylesheets/style.css');
-});
-```
-
-
-### Sass
-
-The `sass` method allows you to compile [Sass](http://sass-lang.com/) into CSS. Assuming your Sass files are stored at `resources/assets/sass`, you may use the method like so:
-
-```javascript
-elixir(function(mix) {
- mix.sass('app.scss');
-});
-```
-
-Again, like the `less` method, you may compile multiple Sass files into a single CSS file, and even customize the output directory of the resulting CSS:
-
-```javascript
-elixir(function(mix) {
- mix.sass([
- 'app.scss',
- 'controllers.scss'
- ], 'public/assets/css');
-});
-```
-
-#### Custom Paths
-
-While it's recommended that you use Laravel's default asset directories, if you require a different base directory, you may begin any file path with `./`. This instructs Elixir to begin at the project root, rather than using the default base directory.
-
-For example, to compile a file located at `app/assets/sass/app.scss` and output the results to `public/css/app.css`, you would make the following call to the `sass` method:
-
-```javascript
-elixir(function(mix) {
- mix.sass('./app/assets/sass/app.scss');
-});
-```
-
-
-### Stylus
-
-The `stylus` method may be used to compile [Stylus](http://stylus-lang.com/) into CSS. Assuming that your Stylus files are stored in `resources/assets/stylus`, you may call the method like so:
-
-```javascript
-elixir(function(mix) {
- mix.stylus('app.styl');
-});
-```
-
-> {tip} This method's signature is identical to both `mix.less()` and `mix.sass()`.
-
-
-### Plain CSS
-
-If you would just like to combine some plain CSS stylesheets into a single file, you may use the `styles` method. Paths passed to this method are relative to the `resources/assets/css` directory and the resulting CSS will be placed in `public/css/all.css`:
-
-```javascript
-elixir(function(mix) {
- mix.styles([
- 'normalize.css',
- 'main.css'
- ]);
-});
-```
-
-You may also instruct Elixir to write the resulting file to a custom directory or file by passing a second argument to the `styles` method:
-
-```javascript
-elixir(function(mix) {
- mix.styles([
- 'normalize.css',
- 'main.css'
- ], 'public/assets/css/site.css');
-});
-```
-
-
-### Source Maps
-
-In Elixir, source maps are enabled by default and provide better debugging information to your browser's developer tools when using compiled assets. For each relevant file that is compiled, you will find a companion `*.css.map` or `*.js.map` file in the same directory.
-
-If you do not want source maps generated for your application, you may disable them using the `sourcemaps` configuration option:
-
-```javascript
-elixir.config.sourcemaps = false;
-
-elixir(function(mix) {
- mix.sass('app.scss');
-});
-```
-
-
-## Working With Scripts
-
-Elixir provides several features to help you work with your JavaScript files, such as compiling ECMAScript 2015, module bundling, minification, and simply concatenating plain JavaScript files.
-
-When writing ES2015 with modules, you have your choice between [Webpack](http://webpack.github.io) and [Rollup](http://rollupjs.org/). If these tools are foreign to you, don't worry, Elixir will handle all of the hard work behind the scenes. By default, the Laravel `gulpfile` uses `webpack` to compile Javascript, but you are free to use any module bundler you like.
-
-
-### Webpack
-
-The `webpack` method may be used to compile and bundle [ECMAScript 2015](https://babeljs.io/docs/learn-es2015/) into plain JavaScript. This function accepts a file path relative to the `resources/assets/js` directory and generates a single bundled file in the `public/js` directory:
-
-```javascript
-elixir(function(mix) {
- mix.webpack('app.js');
-});
-```
-
-To choose a different output or base directory, simply specify your desired paths with a leading `.`. Then you may specify the paths relative to the root of your application. For example, to compile `app/assets/js/app.js` to `public/dist/app.js`:
-
-```javascript
-elixir(function(mix) {
- mix.webpack(
- './resources/assets/js/app.js',
- './public/dist'
- );
-});
-```
-
-If you'd like to leverage more of Webpack's functionality, Elixir will read any `webpack.config.js` file that is in your project root and [factor its configuration](https://webpack.github.io/docs/configuration.html) into the build process.
-
-
-
-### Rollup
-
-Similar to Webpack, Rollup is a next-generation bundler for ES2015. This function accepts an array of files relative to the `resources/assets/js` directory, and generates a single file in the `public/js` directory:
-
-```javascript
-elixir(function(mix) {
- mix.rollup('app.js');
-});
-```
-
-Like the `webpack` method, you may customize the location of the input and output files given to the `rollup` method:
-
- elixir(function(mix) {
- mix.rollup(
- './resources/assets/js/app.js',
- './public/dist'
- );
- });
-
-
-### Scripts
-
-If you have multiple JavaScript files that you would like to combine into a single file, you may use the `scripts` method, which provides automatic source maps, concatenation, and minification.
-
-The `scripts` method assumes all paths are relative to the `resources/assets/js` directory, and will place the resulting JavaScript in `public/js/all.js` by default:
-
-```javascript
-elixir(function(mix) {
- mix.scripts([
- 'order.js',
- 'forum.js'
- ]);
-});
-```
-
-If you need to concatenate multiple sets of scripts into different files, you may make multiple calls to the `scripts` method. The second argument given to the method determines the resulting file name for each concatenation:
-
-```javascript
-elixir(function(mix) {
- mix.scripts(['app.js', 'controllers.js'], 'public/js/app.js')
- .scripts(['forum.js', 'threads.js'], 'public/js/forum.js');
-});
-```
-
-If you need to combine all of the scripts in a given directory, you may use the `scriptsIn` method. The resulting JavaScript will be placed in `public/js/all.js`:
-
-```javascript
-elixir(function(mix) {
- mix.scriptsIn('public/js/some/directory');
-});
-```
-
-> {tip} If you intend to concatenate multiple pre-minified vendor libraries, such as jQuery, instead consider using `mix.combine()`. This will combine the files, while omitting the source map and minification steps. As a result, compile times will drastically improve.
-
-
-
-## Copying Files & Directories
-
-The `copy` method may be used to copy files and directories to new locations. All operations are relative to the project's root directory:
-
-```javascript
-elixir(function(mix) {
- mix.copy('vendor/foo/bar.css', 'public/css/bar.css');
-});
-```
-
-
-## Versioning / Cache Busting
-
-Many developers suffix their compiled assets with a timestamp or unique token to force browsers to load the fresh assets instead of serving stale copies of the code. Elixir can handle this for you using the `version` method.
-
-The `version` method accepts a file name relative to the `public` directory, and will append a unique hash to the filename, allowing for cache-busting. For example, the generated file name will look something like: `all-16d570a7.css`:
-
-```javascript
-elixir(function(mix) {
- mix.version('css/all.css');
-});
-```
-
-After generating the versioned file, you may use Laravel's global `elixir` helper within your [views](/docs/{{version}}/views) to load the appropriately hashed asset. The `elixir` function will automatically determine the current name of the hashed file:
-
-
-
-#### Versioning Multiple Files
-
-You may pass an array to the `version` method to version multiple files:
-
-```javascript
-elixir(function(mix) {
- mix.version(['css/all.css', 'js/app.js']);
-});
-```
-
-Once the files have been versioned, you may use the `elixir` helper function to generate links to the proper hashed files. Remember, you only need to pass the name of the un-hashed file to the `elixir` helper function. The helper will use the un-hashed name to determine the current hashed version of the file:
-
-
-
-
-
-
-## BrowserSync
-
-BrowserSync automatically refreshes your web browser after you make changes to your assets. The `browserSync` method accepts a JavaScript object with a `proxy` attribute containing the local URL for your application. Then, once you run `gulp watch` you may access your web application using port 3000 (`http://project.dev:3000`) to enjoy browser syncing:
-
-```javascript
-elixir(function(mix) {
- mix.browserSync({
- proxy: 'project.dev'
- });
-});
-```
diff --git a/eloquent-mutators.md b/eloquent-mutators.md
index 6fddd3a..5cceb29 100644
--- a/eloquent-mutators.md
+++ b/eloquent-mutators.md
@@ -43,7 +43,7 @@ To define an accessor, create a `getFooAttribute` method on your model where `Fo
}
}
-As you can see, the original value of the column is passed to the accessor, allowing you to manipulate and return the value. To access the value of the mutator, you may simply access the `first_name` attribute on a model instance:
+As you can see, the original value of the column is passed to the accessor, allowing you to manipulate and return the value. To access the value of the accessor, you may simply access the `first_name` attribute on a model instance:
$user = App\User::find(1);
diff --git a/eloquent-relationships.md b/eloquent-relationships.md
index fa31cce..f60a7ba 100644
--- a/eloquent-relationships.md
+++ b/eloquent-relationships.md
@@ -12,6 +12,7 @@
- [Querying Relations](#querying-relations)
- [Relationship Methods Vs. Dynamic Properties](#relationship-methods-vs-dynamic-properties)
- [Querying Relationship Existence](#querying-relationship-existence)
+ - [Querying Relationship Absence](#querying-relationship-absence)
- [Counting Related Models](#counting-related-models)
- [Eager Loading](#eager-loading)
- [Constraining Eager Loads](#constraining-eager-loads)
@@ -38,7 +39,7 @@ Database tables are often related to one another. For example, a blog post may h
## Defining Relationships
-Eloquent relationships are defined as functions on your Eloquent model classes. Since, like Eloquent models themselves, relationships also serve as powerful [query builders](/docs/{{version}}/queries), defining relationships as functions provides powerful method chaining and querying capabilities. For example, we may chain additional constraints on this `posts` relationship:
+Eloquent relationships are defined as methods on your Eloquent model classes. Since, like Eloquent models themselves, relationships also serve as powerful [query builders](/docs/{{version}}/queries), defining relationships as methods provides powerful method chaining and querying capabilities. For example, we may chain additional constraints on this `posts` relationship:
$user->posts()->where('active', 1)->get();
@@ -66,7 +67,7 @@ A one-to-one relationship is a very basic relation. For example, a `User` model
}
}
-The first argument passed to the `hasOne` method is the name of the related model. Once the relationship is defined, we may retrieve the related record using Eloquent's dynamic properties. Dynamic properties allow you to access relationship functions as if they were properties defined on the model:
+The first argument passed to the `hasOne` method is the name of the related model. Once the relationship is defined, we may retrieve the related record using Eloquent's dynamic properties. Dynamic properties allow you to access relationship methods as if they were properties defined on the model:
$phone = User::find(1)->phone;
@@ -143,7 +144,7 @@ A "one-to-many" relationship is used to define relationships where a single mode
Remember, Eloquent will automatically determine the proper foreign key column on the `Comment` model. By convention, Eloquent will take the "snake case" name of the owning model and suffix it with `_id`. So, for this example, Eloquent will assume the foreign key on the `Comment` model is `post_id`.
-Once the relationship has been defined, we can access the collection of comments by accessing the `comments` property. Remember, since Eloquent provides "dynamic properties", we can access relationship functions as if they were defined as properties on the model:
+Once the relationship has been defined, we can access the collection of comments by accessing the `comments` property. Remember, since Eloquent provides "dynamic properties", we can access relationship methods as if they were defined as properties on the model:
$comments = App\Post::find(1)->comments;
@@ -304,10 +305,31 @@ You can also filter the results returned by `belongsToMany` using the `wherePivo
return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
+#### Defining Custom Intermediate Table Models
+
+If you would like to define a custom model to represent the intermediate table of your relationship, you may call the `using` method when defining the relationship. All custom models used to represent intermediate tables of relationships must extend the `Illuminate\Database\Eloquent\Relations\Pivot` class:
+
+ belongsToMany('App\User')->using('App\UserRole');
+ }
+ }
+
### Has Many Through
-The "has-many-through" relationship provides a convenient short-cut for accessing distant relations via an intermediate relation. For example, a `Country` model might have many `Post` models through an intermediate `User` model. In this example, you could easily gather all blog posts for a given country. Let's look at the tables required to define this relationship:
+The "has-many-through" relationship provides a convenient shortcut for accessing distant relations via an intermediate relation. For example, a `Country` model might have many `Post` models through an intermediate `User` model. In this example, you could easily gather all blog posts for a given country. Let's look at the tables required to define this relationship:
countries
id - integer
@@ -452,8 +474,8 @@ By default, Laravel will use the fully qualified class name to store the type of
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
- 'posts' => App\Post::class,
- 'videos' => App\Video::class,
+ 'posts' => 'App\Post',
+ 'videos' => 'App\Video',
]);
You may register the `morphMap` in the `boot` function of your `AppServiceProvider` or create a separate service provider if you wish.
@@ -553,7 +575,7 @@ You may also retrieve the owner of a polymorphic relation from the polymorphic m
## Querying Relations
-Since all types of Eloquent relationships are defined via functions, you may call those functions to obtain an instance of the relationship without actually executing the relationship queries. In addition, all types of Eloquent relationships also serve as [query builders](/docs/{{version}}/queries), allowing you to continue to chain constraints onto the relationship query before finally executing the SQL against your database.
+Since all types of Eloquent relationships are defined via methods, you may call those methods to obtain an instance of the relationship without actually executing the relationship queries. In addition, all types of Eloquent relationships also serve as [query builders](/docs/{{version}}/queries), allowing you to continue to chain constraints onto the relationship query before finally executing the SQL against your database.
For example, imagine a blog system in which a `User` model has many associated `Post` models:
@@ -620,6 +642,19 @@ If you need even more power, you may use the `whereHas` and `orWhereHas` methods
$query->where('content', 'like', 'foo%');
})->get();
+
+### Querying Relationship Absence
+
+When accessing the records for a model, you may wish to limit your results based on the absence of a relationship. For example, imagine you want to retrieve all blog posts that **don't** have any comments. To do so, you may pass the name of the relationship to the `doesntHave` method:
+
+ $posts = App\Post::doesntHave('comments')->get();
+
+If you need even more power, you may use the `whereDoesntHave` method to put "where" conditions on your `doesntHave` queries. This method allows you to add customized constraints to a relationship constraint, such as checking the content of a comment:
+
+ $posts = Post::whereDoesntHave('comments', function ($query) {
+ $query->where('content', 'like', 'foo%');
+ })->get();
+
### Counting Related Models
@@ -631,7 +666,7 @@ If you want to count the number of results from a relationship without actually
echo $post->comments_count;
}
-You may add retrieve the "counts" for multiple relations as well as add constraints to the queries:
+You may add the "counts" for multiple relations as well as add constraints to the queries:
$posts = Post::withCount(['votes', 'comments' => function ($query) {
$query->where('content', 'like', 'foo%');
@@ -830,6 +865,12 @@ If you do not want to detach existing IDs, you may use the `syncWithoutDetaching
$user->roles()->syncWithoutDetaching([1, 2, 3]);
+#### Toggling Associations
+
+The many-to-many relationship also provides a `toggle` method which "toggles" the attachment status of the given IDs. If the given ID is currently attached, it will be detached. Likewise, if it is currently detached, it will be attached:
+
+ $user->roles()->toggle([1, 2, 3]);
+
#### Saving Additional Data On A Pivot Table
When working with a many-to-many relationship, the `save` method accepts an array of additional intermediate table attributes as its second argument:
@@ -842,7 +883,7 @@ If you need to update an existing row in your pivot table, you may use `updateEx
$user = App\User::find(1);
- $user->roles()->updateExistingPivot($roleId, $attributes);
+ $user->roles()->updateExistingPivot($roleId, $attributes);
## Touching Parent Timestamps
diff --git a/eloquent.md b/eloquent.md
index c92809a..ad122be 100644
--- a/eloquent.md
+++ b/eloquent.md
@@ -20,6 +20,7 @@
- [Global Scopes](#global-scopes)
- [Local Scopes](#local-scopes)
- [Events](#events)
+ - [Observers](#observers)
## Introduction
@@ -124,6 +125,16 @@ If you need to customize the format of your timestamps, set the `$dateFormat` pr
protected $dateFormat = 'U';
}
+If you need to customize the names of the columns used to store the timestamps, you may set the `CREATED_AT` and `UPDATED_AT` constants in your model:
+
+ {note} When issuing a mass update via Eloquent, the `saved` and `updated` model events will be not be fired for the updated models. This is because the models are never actually retrieved when issuing a mass update.
+> {note} When issuing a mass update via Eloquent, the `saved` and `updated` model events will not be fired for the updated models. This is because the models are never actually retrieved when issuing a mass update.
### Mass Assignment
@@ -334,6 +345,10 @@ Once we have made the attributes mass assignable, we can use the `create` method
$flight = App\Flight::create(['name' => 'Flight 10']);
+If you already have a model instance, you may use the `fill` method to populate it with an array of attributes:
+
+ $flight->fill(['name' => 'Flight 22']);
+
#### Guarding Attributes
While `$fillable` serves as a "white list" of attributes that should be mass assignable, you may also choose to use `$guarded`. The `$guarded` property should contain an array of attributes that you do not want to be mass assignable. All other attributes not in the array will be mass assignable. So, `$guarded` functions like a "black list". Of course, you should use either `$fillable` or `$guarded` - not both. In the example below, all attributes **except for `price`** will be mass assignable:
@@ -366,6 +381,8 @@ If you would like to make all attributes mass assignable, you may define the `$g
### Other Creation Methods
+#### `firstOrCreate`/ `firstOrNew`
+
There are two other methods you may use to create models by mass assigning attributes: `firstOrCreate` and `firstOrNew`. 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 given attributes.
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 call `save` manually to persist it:
@@ -376,6 +393,17 @@ The `firstOrNew` method, like `firstOrCreate` will attempt to locate a record in
// Retrieve the flight by the attributes, or instantiate a new instance...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
+#### `updateOrCreate`
+
+You may also come across situations where you want to update an existing model or create a new model if none exists. Laravel provides an `updateOrCreate` method to do this in one step. Like the `firstOrCreate` method, `updateOrCreate` persists the model, so there's no need to call `save()`:
+
+ // If there's a flight from Oakland to San Diego, set the price to $99.
+ // If no matching model exists, create one.
+ $flight = App\Flight::updateOrCreate(
+ ['departure' => 'Oakland', 'destination' => 'San Diego'],
+ ['price' => 99]
+ );
+
## Deleting Models
@@ -397,10 +425,12 @@ In the example above, we are retrieving the model from the database before calli
#### Deleting Models By Query
-Of course, you may also run a delete query on a set of models. In this example, we will delete all flights that are marked as inactive. Like mass updates, mass deletes will not fire any model events for the models that are deleted:
+Of course, you may also run a delete statement on a set of models. In this example, we will delete all flights that are marked as inactive. Like mass updates, mass deletes will not fire any model events for the models that are deleted:
$deletedRows = App\Flight::where('active', 0)->delete();
+> {note} When executing a mass delete statement via Eloquent, the `deleting` and `deleted` model events will not be fired for the deleted models. This is because the models are never actually retrieved when executing the delete statement.
+
### Soft Deleting
@@ -519,7 +549,7 @@ Writing a global scope is simple. Define a class that implements the `Illuminate
*/
public function apply(Builder $builder, Model $model)
{
- return $builder->where('age', '>', 200);
+ $builder->where('age', '>', 200);
}
}
@@ -577,16 +607,12 @@ Eloquent also allows you to define global scopes using Closures, which is partic
{
parent::boot();
- static::addGlobalScope('age', function(Builder $builder) {
+ static::addGlobalScope('age', function (Builder $builder) {
$builder->where('age', '>', 200);
});
}
}
-The first argument of the `addGlobalScope()` serves as an identifier to remove the scope:
-
- User::withoutGlobalScope('age')->get();
-
#### Removing Global Scopes
If you would like to remove a global scope for a given query, you may use the `withoutGlobalScope` method. The method accepts the class name of the global scope as its only argument:
@@ -621,6 +647,7 @@ Scopes should always return a query builder instance:
/**
* Scope a query to only include popular users.
*
+ * @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
@@ -631,6 +658,7 @@ Scopes should always return a query builder instance:
/**
* Scope a query to only include active users.
*
+ * @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
@@ -660,6 +688,8 @@ Sometimes you may wish to define a scope that accepts parameters. To get started
/**
* Scope a query to only include users of a given type.
*
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param mixed $type
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOfType($query, $type)
@@ -675,17 +705,80 @@ Now, you may pass the parameters when calling the scope:
## Events
-Eloquent models fire several events, allowing you to hook into various points in the model's lifecycle using the following methods: `creating`, `created`, `updating`, `updated`, `saving`, `saved`, `deleting`, `deleted`, `restoring`, `restored`. Events allow you to easily execute code each time a specific model class is saved or updated in the database.
+Eloquent models fire several events, allowing you to hook into the following points in a model's lifecycle: `creating`, `created`, `updating`, `updated`, `saving`, `saved`, `deleting`, `deleted`, `restoring`, `restored`. Events allow you to easily execute code each time a specific model class is saved or updated in the database.
Whenever a new model is saved for the first time, the `creating` and `created` events will fire. If a model already existed in the database and the `save` method is called, the `updating` / `updated` events will fire. However, in both cases, the `saving` / `saved` events will fire.
-For example, let's define an Eloquent event listener in a [service provider](/docs/{{version}}/providers). Within our event listener, we will call the `isValid` method on the given model, and return `false` if the model is not valid. Returning `false` from an Eloquent event listener will cancel the `save` / `update` operation:
+To get started, define an `$events` property on your Eloquent model that maps various points of the Eloquent model's lifecycle to your own [event classes](/docs/{{version}}/events):
+
+ UserSaved::class,
+ 'deleted' => UserDeleted::class,
+ ];
+ }
+
+
+### Observers
+
+If you are listening for many events on a given model, you may use observers to group all of your listeners into a single class. Observers classes have method names which reflect the Eloquent events you wish to listen for. Each of these methods receives the model as their only argument. Laravel does not include a default directory for observers, so you may create any directory you like to house your observer classes:
+
+ isValid();
- });
+ User::observe(UserObserver::class);
}
/**
diff --git a/encryption.md b/encryption.md
index a0a9130..397777a 100644
--- a/encryption.md
+++ b/encryption.md
@@ -48,7 +48,15 @@ You may encrypt a value using the `encrypt` helper. All encrypted values are enc
}
}
-> {note} Encrypted values are passed through `serialize` during encryption, which allows for encryption of objects and arrays. Thus, non-PHP clients receiving encrypted values will need to `unserialize` the data.
+#### Encrypting Without Serialization
+
+Encrypted values are passed through `serialize` during encryption, which allows for encryption of objects and arrays. Thus, non-PHP clients receiving encrypted values will need to `unserialize` the data. If you would like to encrypt and decrypt values without serialization, you may use the `encryptString` and `decryptString` methods of the `Crypt` facade:
+
+ use Illuminate\Support\Facades\Crypt;
+
+ $encrypted = Crypt::encryptString('Hello world.');
+
+ $decrypted = Crypt::decryptString($encrypted);
#### Decrypting A Value
diff --git a/envoy.md b/envoy.md
index 783ef30..2155e6a 100644
--- a/envoy.md
+++ b/envoy.md
@@ -30,7 +30,7 @@ Since global Composer libraries can sometimes cause package version conflicts, y
#### Updating Envoy
-You may also use Composer to keep your Envoy installation up to date. Issuing the the `composer global update` command will update all of your globally installed Composer packages:
+You may also use Composer to keep your Envoy installation up to date. Issuing the `composer global update` command will update all of your globally installed Composer packages:
composer global update
@@ -77,7 +77,7 @@ If needed, you may pass option values into Envoy tasks using the command line:
envoy run deploy --branch=master
-You may use access the options in your tasks via Blade's "echo" syntax. Of course, you may also use `if` statements and loops within your tasks. For example, let's verify the presence of the `$branch` variable before executing the `git pull` command:
+You may access the options in your tasks via Blade's "echo" syntax. Of course, you may also use `if` statements and loops within your tasks. For example, let's verify the presence of the `$branch` variable before executing the `git pull` command:
@servers(['web' => '192.168.1.1'])
@@ -167,9 +167,9 @@ If you would like to be prompted for confirmation before running a given task on
Envoy also supports sending notifications to [Slack](https://slack.com) after each task is executed. The `@slack` directive accepts a Slack hook URL and a channel name. You may retrieve your webhook URL by creating an "Incoming WebHooks" integration in your Slack control panel. You should pass the entire webhook URL into the `@slack` directive:
- @after
+ @finished
@slack('webhook-url', '#bots')
- @endafter
+ @endfinished
You may provide one of the following as the channel argument:
diff --git a/errors.md b/errors.md
index d099173..f4bdb32 100644
--- a/errors.md
+++ b/errors.md
@@ -59,7 +59,7 @@ Once this option has been configured, Laravel will log all levels greater than o
If you would like to have complete control over how Monolog is configured for your application, you may use the application's `configureMonologUsing` method. You should place a call to this method in your `bootstrap/app.php` file right before the `$app` variable is returned by the file:
- $app->configureMonologUsing(function($monolog) {
+ $app->configureMonologUsing(function ($monolog) {
$monolog->pushHandler(...);
});
@@ -71,7 +71,7 @@ If you would like to have complete control over how Monolog is configured for yo
### The Report Method
-All exceptions are handled by the `App\Exceptions\Handler` class. This class contains two methods: `report` and `render`. We'll examine each of these methods in detail. The `report` method is used to log exceptions or send them to an external service like [BugSnag](https://bugsnag.com) or [Sentry](https://github.com/getsentry/sentry-laravel). By default, the `report` method simply passes the exception to the base class where the exception is logged. However, you are free to log exceptions however you wish.
+All exceptions are handled by the `App\Exceptions\Handler` class. This class contains two methods: `report` and `render`. We'll examine each of these methods in detail. The `report` method is used to log exceptions or send them to an external service like [Bugsnag](https://bugsnag.com) or [Sentry](https://github.com/getsentry/sentry-laravel). By default, the `report` method simply passes the exception to the base class where the exception is logged. However, you are free to log exceptions however you wish.
For example, if you need to report different types of exceptions in different ways, you may use the PHP `instanceof` comparison operator:
@@ -80,16 +80,16 @@ For example, if you need to report different types of exceptions in different wa
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
- * @param \Exception $e
+ * @param \Exception $exception
* @return void
*/
- public function report(Exception $e)
+ public function report(Exception $exception)
{
- if ($e instanceof CustomException) {
+ if ($exception instanceof CustomException) {
//
}
- return parent::report($e);
+ return parent::report($exception);
}
#### Ignoring Exceptions By Type
@@ -118,16 +118,16 @@ The `render` method is responsible for converting a given exception into an HTTP
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
- * @param \Exception $e
+ * @param \Exception $exception
* @return \Illuminate\Http\Response
*/
- public function render($request, Exception $e)
+ public function render($request, Exception $exception)
{
- if ($e instanceof CustomException) {
+ if ($exception instanceof CustomException) {
return response()->view('errors.custom', [], 500);
}
- return parent::render($request, $e);
+ return parent::render($request, $exception);
}
@@ -149,7 +149,7 @@ Laravel makes it easy to display custom error pages for various HTTP status code
## Logging
-Laravel provides a simple abstraction layer on top of the powerful [Monolog](http://github.com/seldaek/monolog) library. By default, Laravel is configured to create a log file for your application in the `storage/logs` directory. You may write information to the logs using the `Log` [facade](/docs/{{version}}/facades):
+Laravel provides a simple abstraction layer on top of the powerful [Monolog](https://github.com/seldaek/monolog) library. By default, Laravel is configured to create a log file for your application in the `storage/logs` directory. You may write information to the logs using the `Log` [facade](/docs/{{version}}/facades):
+### Manually Registering Events
+
+Typically, events should be registered via the `EventServiceProvider` `$listen` array; however, you may also register Closure based events manually in the `boot` method of your `EventServiceProvider`:
+
+ /**
+ * Register any other events for your application.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ parent::boot();
+
+ Event::listen('event.name', function ($foo, $bar) {
+ //
+ });
+ }
+
+#### Wildcard Event Listeners
+
+You may even register listeners using the `*` as a wildcard parameter, allowing you to catch multiple events on the same listener. Wildcard listeners receive the event name as their first argument, and the entire event data array as their second argument:
+
+ Event::listen('event.*', function ($eventName, array $data) {
+ //
+ });
+
## Defining Events
@@ -53,10 +81,9 @@ An event class is simply a data container which holds the information related to
namespace App\Events;
use App\Order;
- use App\Events\Event;
use Illuminate\Queue\SerializesModels;
- class OrderShipped extends Event
+ class OrderShipped
{
use SerializesModels;
@@ -138,6 +165,34 @@ To specify that a listener should be queued, add the `ShouldQueue` interface to
That's it! Now, when this listener is called for an event, it will be automatically queued by the event dispatcher using Laravel's [queue system](/docs/{{version}}/queues). If no exceptions are thrown when the listener is executed by the queue, the queued job will automatically be deleted after it has finished processing.
+#### Customizing The Queue Connection & Queue Name
+
+If you would like to customize the queue connection and queue name used by an event listener, you may define `$connection` and `$queue` properties on your listener class:
+
+
### Manually Accessing The Queue
@@ -163,10 +218,38 @@ If you need to manually access the listener's underlying queue job's `delete` an
}
}
-
-## Firing Events
+
+### Handling Failed Jobs
+
+Sometimes your queued event listeners may fail. If queued listener exceeds the maximum number of attempts as defined by your queue worker, the `failed` method will be called on your listener. The `failed` method receives the event instance and the exception that caused the failure:
+
+
+## Dispatching Events
-To fire an event, you may pass an instance of the event to the `event` helper. The helper will dispatch the event to all of its registered listeners. Since the `event` helper is globally available, you may call it from anywhere in your application:
+To dispatch an event, you may pass an instance of the event to the `event` helper. The helper will dispatch the event to all of its registered listeners. Since the `event` helper is globally available, you may call it from anywhere in your application:
{tip} When testing, it can be helpful to assert that certain events were fired without actually triggering their listeners. Laravel's [built-in testing helpers](/docs/{{version}}/mocking#mocking-events) makes it a cinch.
+> {tip} When testing, it can be helpful to assert that certain events were dispatched without actually triggering their listeners. Laravel's [built-in testing helpers](/docs/{{version}}/mocking#mocking-events) makes it a cinch.
## Event Subscribers
diff --git a/facades.md b/facades.md
index fc9b629..d1ee28a 100644
--- a/facades.md
+++ b/facades.md
@@ -27,7 +27,7 @@ Throughout the Laravel documentation, many of the examples will use facades to d
Facades have many benefits. They provide a terse, memorable syntax that allows you to use Laravel's features without remembering long class names that must be injected or configured manually. Furthermore, because of their unique usage of PHP's dynamic methods, they are easy to test.
-However, some care must be taken when using facades. The primary danger of facades is class scope creep. Since facades are so easy to use and do not require injection, it can be easy to let your classes continue to grow and use many facades in a single class. Using dependency injection, this potential is mitigated by the visual feedback a large constructor gives you that your class is growing too large. So, when using facades, pay special attention to the size of your class so that it's scope of responsibility stays narrow.
+However, some care must be taken when using facades. The primary danger of facades is class scope creep. Since facades are so easy to use and do not require injection, it can be easy to let your classes continue to grow and use many facades in a single class. Using dependency injection, this potential is mitigated by the visual feedback a large constructor gives you that your class is growing too large. So, when using facades, pay special attention to the size of your class so that its scope of responsibility stays narrow.
> {tip} When building a third-party package that interacts with Laravel, it's better to inject [Laravel contracts](/docs/{{version}}/contracts) instead of using facades. Since packages are built outside of Laravel itself, you will not have access to Laravel's facade testing helpers.
@@ -150,39 +150,40 @@ Below you will find every facade and its underlying class. This is a useful tool
Facade | Class | Service Container Binding
------------- | ------------- | -------------
-App | [Illuminate\Foundation\Application](http://laravel.com/api/{{version}}/Illuminate/Foundation/Application.html) | `app`
-Artisan | [Illuminate\Contracts\Console\Kernel](http://laravel.com/api/{{version}}/Illuminate/Contracts/Console/Kernel.html) | `artisan`
-Auth | [Illuminate\Auth\AuthManager](http://laravel.com/api/{{version}}/Illuminate/Auth/AuthManager.html) | `auth`
-Blade | [Illuminate\View\Compilers\BladeCompiler](http://laravel.com/api/{{version}}/Illuminate/View/Compilers/BladeCompiler.html) | `blade.compiler`
-Bus | [Illuminate\Contracts\Bus\Dispatcher](http://laravel.com/api/{{version}}/Illuminate/Contracts/Bus/Dispatcher.html) |
-Cache | [Illuminate\Cache\Repository](http://laravel.com/api/{{version}}/Illuminate/Cache/Repository.html) | `cache`
-Config | [Illuminate\Config\Repository](http://laravel.com/api/{{version}}/Illuminate/Config/Repository.html) | `config`
-Cookie | [Illuminate\Cookie\CookieJar](http://laravel.com/api/{{version}}/Illuminate/Cookie/CookieJar.html) | `cookie`
-Crypt | [Illuminate\Encryption\Encrypter](http://laravel.com/api/{{version}}/Illuminate/Encryption/Encrypter.html) | `encrypter`
-DB | [Illuminate\Database\DatabaseManager](http://laravel.com/api/{{version}}/Illuminate/Database/DatabaseManager.html) | `db`
-DB (Instance) | [Illuminate\Database\Connection](http://laravel.com/api/{{version}}/Illuminate/Database/Connection.html) |
-Event | [Illuminate\Events\Dispatcher](http://laravel.com/api/{{version}}/Illuminate/Events/Dispatcher.html) | `events`
-File | [Illuminate\Filesystem\Filesystem](http://laravel.com/api/{{version}}/Illuminate/Filesystem/Filesystem.html) | `files`
-Gate | [Illuminate\Contracts\Auth\Access\Gate](http://laravel.com/api/5.1/Illuminate/Contracts/Auth/Access/Gate.html) |
-Hash | [Illuminate\Contracts\Hashing\Hasher](http://laravel.com/api/{{version}}/Illuminate/Contracts/Hashing/Hasher.html) | `hash`
-Lang | [Illuminate\Translation\Translator](http://laravel.com/api/{{version}}/Illuminate/Translation/Translator.html) | `translator`
-Log | [Illuminate\Log\Writer](http://laravel.com/api/{{version}}/Illuminate/Log/Writer.html) | `log`
-Mail | [Illuminate\Mail\Mailer](http://laravel.com/api/{{version}}/Illuminate/Mail/Mailer.html) | `mailer`
-Password | [Illuminate\Auth\Passwords\PasswordBroker](http://laravel.com/api/{{version}}/Illuminate/Auth/Passwords/PasswordBroker.html) | `auth.password`
-Queue | [Illuminate\Queue\QueueManager](http://laravel.com/api/{{version}}/Illuminate/Queue/QueueManager.html) | `queue`
-Queue (Instance) | [Illuminate\Contracts\Queue\Queue](http://laravel.com/api/{{version}}/Illuminate/Contracts/Queue/Queue.html) | `queue`
-Queue (Base Class) | [Illuminate\Queue\Queue](http://laravel.com/api/{{version}}/Illuminate/Queue/Queue.html) |
-Redirect | [Illuminate\Routing\Redirector](http://laravel.com/api/{{version}}/Illuminate/Routing/Redirector.html) | `redirect`
-Redis | [Illuminate\Redis\Database](http://laravel.com/api/{{version}}/Illuminate/Redis/Database.html) | `redis`
-Request | [Illuminate\Http\Request](http://laravel.com/api/{{version}}/Illuminate/Http/Request.html) | `request`
-Response | [Illuminate\Contracts\Routing\ResponseFactory](http://laravel.com/api/{{version}}/Illuminate/Contracts/Routing/ResponseFactory.html) |
-Route | [Illuminate\Routing\Router](http://laravel.com/api/{{version}}/Illuminate/Routing/Router.html) | `router`
-Schema | [Illuminate\Database\Schema\Blueprint](http://laravel.com/api/{{version}}/Illuminate/Database/Schema/Blueprint.html) |
-Session | [Illuminate\Session\SessionManager](http://laravel.com/api/{{version}}/Illuminate/Session/SessionManager.html) | `session`
-Session (Instance) | [Illuminate\Session\Store](http://laravel.com/api/{{version}}/Illuminate/Session/Store.html) |
-Storage | [Illuminate\Contracts\Filesystem\Factory](http://laravel.com/api/{{version}}/Illuminate/Contracts/Filesystem/Factory.html) | `filesystem`
-URL | [Illuminate\Routing\UrlGenerator](http://laravel.com/api/{{version}}/Illuminate/Routing/UrlGenerator.html) | `url`
-Validator | [Illuminate\Validation\Factory](http://laravel.com/api/{{version}}/Illuminate/Validation/Factory.html) | `validator`
-Validator (Instance) | [Illuminate\Validation\Validator](http://laravel.com/api/{{version}}/Illuminate/Validation/Validator.html) |
-View | [Illuminate\View\Factory](http://laravel.com/api/{{version}}/Illuminate/View/Factory.html) | `view`
-View (Instance) | [Illuminate\View\View](http://laravel.com/api/{{version}}/Illuminate/View/View.html) |
+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`
+Blade | [Illuminate\View\Compilers\BladeCompiler](https://laravel.com/api/{{version}}/Illuminate/View/Compilers/BladeCompiler.html) | `blade.compiler`
+Bus | [Illuminate\Contracts\Bus\Dispatcher](https://laravel.com/api/{{version}}/Illuminate/Contracts/Bus/Dispatcher.html) |
+Cache | [Illuminate\Cache\Repository](https://laravel.com/api/{{version}}/Illuminate/Cache/Repository.html) | `cache`
+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`
+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) |
+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`
+Lang | [Illuminate\Translation\Translator](https://laravel.com/api/{{version}}/Illuminate/Translation/Translator.html) | `translator`
+Log | [Illuminate\Log\Writer](https://laravel.com/api/{{version}}/Illuminate/Log/Writer.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`
+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`
+Queue (Base Class) | [Illuminate\Queue\Queue](https://laravel.com/api/{{version}}/Illuminate/Queue/Queue.html) |
+Redirect | [Illuminate\Routing\Redirector](https://laravel.com/api/{{version}}/Illuminate/Routing/Redirector.html) | `redirect`
+Redis | [Illuminate\Redis\Database](https://laravel.com/api/{{version}}/Illuminate/Redis/Database.html) | `redis`
+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) |
+Route | [Illuminate\Routing\Router](https://laravel.com/api/{{version}}/Illuminate/Routing/Router.html) | `router`
+Schema | [Illuminate\Database\Schema\Blueprint](https://laravel.com/api/{{version}}/Illuminate/Database/Schema/Blueprint.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) |
+Storage | [Illuminate\Contracts\Filesystem\Factory](https://laravel.com/api/{{version}}/Illuminate/Contracts/Filesystem/Factory.html) | `filesystem`
+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) |
diff --git a/filesystem.md b/filesystem.md
index 5d5a2f8..3de773b 100644
--- a/filesystem.md
+++ b/filesystem.md
@@ -4,7 +4,7 @@
- [Configuration](#configuration)
- [The Public Disk](#the-public-disk)
- [The Local Driver](#the-local-driver)
- - [Other Driver Prerequisites](#other-driver-prerequisites)
+ - [Driver Prerequisites](#driver-prerequisites)
- [Obtaining Disk Instances](#obtaining-disk-instances)
- [Retrieving Files](#retrieving-files)
- [File URLs](#file-urls)
@@ -48,8 +48,8 @@ When using the `local` driver, all file operations are relative to the `root` di
Storage::disk('local')->put('file.txt', 'Contents');
-
-### Other Driver Prerequisites
+
+### Driver Prerequisites
#### Composer Packages
@@ -58,6 +58,10 @@ Before using the S3 or Rackspace drivers, you will need to install the appropria
- Amazon S3: `league/flysystem-aws-s3-v3 ~1.0`
- Rackspace: `league/flysystem-rackspace ~1.0`
+#### S3 Driver Configuration
+
+The S3 driver configuration information is located in your `config/filesystems.php` configuration file. This file contains an example configuration array for an S3 driver. You are free to modify this array with your own S3 configuration and credentials.
+
#### FTP Driver Configuration
Laravel's Flysystem integrations works great with FTP; however, a sample configuration is not included with the framework's default `filesystems.php` configuration file. If you need to configure a FTP filesystem, you may use the example configuration below:
@@ -123,7 +127,18 @@ When using the `local` or `s3` drivers, you may use the `url` method to get the
$url = Storage::url('file1.jpg');
-> {note} Remember, if you are using the `local` driver, all files that should be publicly accessible should be placed in the `storage/public` directory. Furthermore, you should [create a symbolic link](#the-public-disk) to the `storage/public` directory.
+> {note} Remember, if you are using the `local` driver, all files that should be publicly accessible should be placed in the `storage/app/public` directory. Furthermore, you should [create a symbolic link](#the-public-disk) at `public/storage` which points to the `storage/app/public` directory.
+
+#### Local URL Host Customization
+
+If you would like to pre-define the host for files stored on a disk using the `local` driver, you may add a `url` option to the disk's configuration array:
+
+ 'public' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/public'),
+ 'url' => env('APP_URL').'/storage',
+ 'visibility' => 'public',
+ ],
### File Metadata
@@ -141,7 +156,7 @@ The `lastModified` method returns the UNIX timestamp of the last time the file w
## Storing Files
-The `put` method may be used to store a file on disk. You may also pass a PHP `resource` to the `put` method, which will use Flysystem's underlying stream support. Using streams is greatly recommended when dealing with large files:
+The `put` method may be used to store raw file contents on a disk. You may also pass a PHP `resource` to the `put` method, which will use Flysystem's underlying stream support. Using streams is greatly recommended when dealing with large files:
use Illuminate\Support\Facades\Storage;
@@ -149,6 +164,24 @@ The `put` method may be used to store a file on disk. You may also pass a PHP `r
Storage::put('file.jpg', $resource);
+#### Automatic Streaming
+
+If you would like Laravel to automatically manage streaming a given file to your storage location, you may use the `putFile` or `putFileAs` method. This method accepts either a `Illuminate\Http\File` or `Illuminate\Http\UploadedFile` instance and will automatically stream the file to your desire location:
+
+ use Illuminate\Http\File;
+
+ // Automatically generate a unique ID for file name...
+ Storage::putFile('photos', new File('/path/to/photo'));
+
+ // Manually specify a file name...
+ Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg');
+
+There are a few important things to note about the `putFile` method. Note that we only specified a directory name, not a file name. By default, the `putFile` method will generate a unique ID to serve as the file name. The path to the file will be returned by the `putFile` method so you can store the path, including the generated file name, in your database.
+
+The `putFile` and `putFileAs` methods also accept an argument to specify the "visibility" of the stored file. This is particularly useful if you are storing the file on a cloud disk such as S3 and would like the file to be publicly accessible:
+
+ Storage::putFile('photos', new File('/path/to/photo'), 'public');
+
#### Prepending & Appending To Files
The `prepend` and `append` methods allow you to write to the beginning or end of a file:
@@ -193,9 +226,11 @@ In web applications, one of the most common use-cases for storing files is stori
}
}
-There are a few important things to note about this example. Note that we only specified a directory name, not a file name. By default, the `store` method will automatically generate a filename based on the contents of the file. This is accomplished by taking a MD5 hash of the file's contents. The path to the file will be returned by the `store` method so you can store the path, including the generated file name, in your database.
+There are a few important things to note about this example. Note that we only specified a directory name, not a file name. By default, the `store` method will generate a unique ID to serve as the file name. The path to the file will be returned by the `store` method so you can store the path, including the generated file name, in your database.
-> {note} If you are receiving very large file uploads, you may wish to manually specify the file name as shown below. Calculating an MD5 hash for extremely large files can be memory intensive.
+You may also call the `putFile` method on the `Storage` facade to perform the same file manipulation as the example above:
+
+ $path = Storage::putFile('avatars', $request->file('avatar'));
#### Specifying A File Name
@@ -205,6 +240,12 @@ If you would not like a file name to be automatically assigned to your stored fi
'avatars', $request->user()->id
);
+Of course, you may also use the `putFileAs` method on the `Storage` facade, which will perform the same file manipulation as the example above:
+
+ $path = Storage::putFileAs(
+ 'avatars', $request->file('avatar'), $request->user()->id
+ );
+
#### Specifying A Disk
By default, this method will use your default disk. If you would like to specify another disk, pass the disk name as the second argument to the `store` method:
@@ -216,7 +257,7 @@ By default, this method will use your default disk. If you would like to specify
### File Visibility
-In Laravel's Flysystem integration, "visibility" is an abstraction of file permissions across multiple platforms. Files may either be declared `public` or `private`. When a file is declared `public`, you are indicating that the file should generally be accessile to others. For example, when using the S3 driver, you may retrieve URLs for `public` files.
+In Laravel's Flysystem integration, "visibility" is an abstraction of file permissions across multiple platforms. Files may either be declared `public` or `private`. When a file is declared `public`, you are indicating that the file should generally be accessible to others. For example, when using the S3 driver, you may retrieve URLs for `public` files.
You can set the visibility when setting the file via the `put` method:
@@ -301,7 +342,7 @@ In order to set up the custom filesystem you will need to create a [service prov
*/
public function boot()
{
- Storage::extend('dropbox', function($app, $config) {
+ Storage::extend('dropbox', function ($app, $config) {
$client = new DropboxClient(
$config['accessToken'], $config['clientIdentifier']
);
@@ -323,4 +364,4 @@ In order to set up the custom filesystem you will need to create a [service prov
The first argument of the `extend` method is the name of the driver and the second is a Closure that receives the `$app` and `$config` variables. The resolver Closure must return an instance of `League\Flysystem\Filesystem`. The `$config` variable contains the values defined in `config/filesystems.php` for the specified disk.
-Once you have created the service provider to register the extension, you may use the `dropbox` driver in your `config/filesystem.php` configuration file.
+Once you have created the service provider to register the extension, you may use the `dropbox` driver in your `config/filesystems.php` configuration file.
diff --git a/frontend.md b/frontend.md
new file mode 100644
index 0000000..c2a779b
--- /dev/null
+++ b/frontend.md
@@ -0,0 +1,72 @@
+# JavaScript & CSS Scaffolding
+
+- [Introduction](#introduction)
+- [Writing CSS](#writing-css)
+- [Writing JavaScript](#writing-javascript)
+ - [Writing Vue Components](#writing-vue-components)
+
+
+## Introduction
+
+While Laravel does not dictate which JavaScript or CSS pre-processors you use, it does provide a basic starting point using [Bootstrap](https://getbootstrap.com/) and [Vue](https://vuejs.org) that will be helpful for many applications. By default, Laravel uses [NPM](https://www.npmjs.org) to install both of these frontend packages.
+
+#### CSS
+
+[Laravel Mix](/docs/{{version}}/mix) provides a clean, expressive API over compiling SASS or Less, which are extensions of plain CSS that add variables, mixins, and other powerful features that make working with CSS much more enjoyable.
+
+In this document, we will briefly discuss CSS compilation in general; however, you should consult the full [Laravel Mix documentation](/docs/{{version}}/mix) for more information on compiling SASS or Less.
+
+#### JavaScript
+
+Laravel does not require you to use a specific JavaScript framework or library to build your applications. In fact, you don't have to use JavaScript at all. However, Laravel does include some basic scaffolding to make it easier to get started writing modern JavaScript using the [Vue](https://vuejs.org) library. Vue provides an expressive API for building robust JavaScript applications using components.
+
+
+## Writing CSS
+
+The Laravel `package.json` file includes the `bootstrap-sass` package to help you get started prototyping your application's frontend using Bootstrap. However, feel free to add or remove packages from the `package.json` file as needed for your own application. You are not required to use the Bootstrap framework to build your Laravel application - it is simply provided as a good starting point for those who choose to use it.
+
+Before compiling your CSS, install your project's frontend dependencies using NPM:
+
+ npm install
+
+Once the dependencies have been installed using `npm install`, you can compile your SASS files to plain CSS using [Laravel Mix](/docs/{{version}}/mix#working-with-stylesheets). The `npm run dev` command will process the instructions in your `webpack.mix.js` file. Typically, your compiled CSS will be placed in the `public/css` directory:
+
+ npm run dev
+
+The default `webpack.mix.js` included with Laravel will compile the `resources/assets/sass/app.scss` SASS file. This `app.scss` file imports a file of SASS variables and loads Bootstrap, which provides a good starting point for most applications. Feel free to customize the `app.scss` file however you wish or even use an entirely different pre-processor by [configuring Laravel Mix](/docs/{{version}}/mix).
+
+
+## Writing JavaScript
+
+All of the JavaScript dependencies required by your application can be found in the `package.json` file in the project's root directory. This file is similar to a `composer.json` file except it specifies JavaScript dependencies instead of PHP dependencies. You can install these dependencies using the [Node package manager (NPM)](https://www.npmjs.org):
+
+ npm install
+
+By default, the Laravel `package.json` file includes a few packages such as `vue` and `axios` to help you get started building your JavaScript application. Feel free to add or remove from the `package.json` file as needed for your own application.
+
+Once the packages are installed, you can use the `npm run dev` command to [compile your assets](/docs/{{version}}/mix). Webpack is a module bundler for modern JavaScript applications. When you run the `npm run dev` command, Webpack will execute the instructions in your `webpack.mix.js` file:
+
+ npm run dev
+
+By default, the Laravel `webpack.mix.js` file compiles your SASS and the `resources/assets/js/app.js` file. Within the `app.js` file you may register your Vue components or, if you prefer a different framework, configure your own JavaScript application. Your compiled JavaScript will typically be placed in the `public/js` directory.
+
+> {tip} The `app.js` file will load the `resources/assets/js/bootstrap.js` file which bootstraps and configures Vue, Axios, jQuery, and all other JavaScript dependencies. If you have additional JavaScript dependencies to configure, you may do so in this file.
+
+
+### Writing Vue Components
+
+By default, fresh Laravel applications contain an `Example.vue` Vue component located in the `resources/assets/js/components` directory. The `Example.vue` file is an example of a [single file Vue component](https://vuejs.org/guide/single-file-components) which defines its JavaScript and HTML template in the same file. Single file components provide a very convenient approach to building JavaScript driven applications. The example component is registered in your `app.js` file:
+
+ Vue.component('example', require('./components/Example.vue'));
+
+To use the component in your application, you may simply drop it into one of your HTML templates. For example, after running the `make:auth` Artisan command to scaffold your application's authentication and registration screens, you could drop the component into the `home.blade.php` Blade template:
+
+ @extends('layouts.app')
+
+ @section('content')
+
+ @endsection
+
+> {tip} Remember, you should run the `npm run dev` command each time you change a Vue component. Or, you may run the `npm run watch` command to monitor and automatically recompile your components each time they are modified.
+
+Of course, if you are interested in learning more about writing Vue components, you should read the [Vue documentation](https://vuejs.org/guide/), which provides a thorough, easy-to-read overview of the entire Vue framework.
diff --git a/helpers.md b/helpers.md
index 44e73e5..2cb4b52 100644
--- a/helpers.md
+++ b/helpers.md
@@ -39,6 +39,7 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct
[array_last](#method-array-last)
[array_only](#method-array-only)
[array_pluck](#method-array-pluck)
+[array_prepend](#method-array-prepend)
[array_pull](#method-array-pull)
[array_set](#method-array-set)
[array_sort](#method-array-sort)
@@ -56,7 +57,7 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct
[base_path](#method-base-path)
[config_path](#method-config-path)
[database_path](#method-database-path)
-[elixir](#method-elixir)
+[mix](#method-mix)
[public_path](#method-public-path)
[resource_path](#method-resource-path)
[storage_path](#method-storage-path)
@@ -96,6 +97,7 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct
[asset](#method-asset)
[secure_asset](#method-secure-asset)
[route](#method-route)
+[secure_url](#method-secure-url)
[url](#method-url)
@@ -110,6 +112,7 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct
[auth](#method-auth)
[back](#method-back)
[bcrypt](#method-bcrypt)
+[cache](#method-cache)
[collect](#method-collect)
[config](#method-config)
[csrf_field](#method-csrf-field)
@@ -119,11 +122,14 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct
[env](#method-env)
[event](#method-event)
[factory](#method-factory)
+[info](#method-info)
+[logger](#method-logger)
[method_field](#method-method-field)
[old](#method-old)
[redirect](#method-redirect)
[request](#method-request)
[response](#method-response)
+[retry](#method-retry)
[session](#method-session)
[value](#method-value)
[view](#method-view)
@@ -252,14 +258,18 @@ The `array_get` function also accepts a default value, which will be returned if
#### `array_has()` {#collection-method}
-The `array_has` function checks that a given item exists in an array using "dot" notation:
+The `array_has` function checks that a given item or items exists in an array using "dot" notation:
- $array = ['products' => ['desk' => ['price' => 100]]];
+ $array = ['product' => ['name' => 'desk', 'price' => 100]];
- $hasDesk = array_has($array, 'products.desk');
+ $hasItem = array_has($array, 'product.name');
// true
+ $hasItems = array_has($array, ['product.price', 'product.discount']);
+
+ // false
+
#### `array_last()` {#collection-method}
@@ -445,7 +455,7 @@ The `app_path` function returns the fully qualified path to the `app` directory.
#### `base_path()` {#collection-method}
-The `base_path` function returns the fully qualified path to the project root. You may also use the `base_path` function to generate a fully qualified path to a given file relative to the application directory:
+The `base_path` function returns the fully qualified path to the project root. You may also use the `base_path` function to generate a fully qualified path to a given file relative to the project root directory:
$path = base_path();
@@ -465,12 +475,12 @@ The `database_path` function returns the fully qualified path to the application
$path = database_path();
-
-#### `elixir()` {#collection-method}
+
+#### `mix()` {#collection-method}
-The `elixir` function gets the path to a [versioned Elixir file](/docs/{{version}}/elixir):
+The `mix` function gets the path to a [versioned Mix file](/docs/{{version}}/mix):
- elixir($file);
+ mix($file);
#### `public_path()` {#collection-method}
@@ -521,7 +531,7 @@ The `class_basename` returns the class name of the given class with the class' n
#### `e()` {#collection-method}
-The `e` function runs `htmlentities` over the given string:
+The `e` function runs `htmlspecialchars` over the given string:
echo e('foo');
@@ -572,6 +582,12 @@ The `str_contains` function determines if the given string contains the given va
// true
+You may also pass an array of values to determine if the given string contains any of the values:
+
+ $value = str_contains('This is my name', ['my', 'foo']);
+
+ // true
+
#### `str_finish()` {#collection-method}
@@ -693,14 +709,14 @@ If the method accepts route parameters, you may pass them as the second argument
Generate a URL for an asset using the current scheme of the request (HTTP or HTTPS):
- $url = asset('img/photo.jpg');
+ $url = asset('img/photo.jpg');
#### `secure_asset()` {#collection-method}
Generate a URL for an asset using HTTPS:
- echo secure_asset('foo/bar.zip', $title, $attributes = []);
+ echo secure_asset('foo/bar.zip', $title, $attributes = []);
#### `route()` {#collection-method}
@@ -713,6 +729,15 @@ If the route accepts parameters, you may pass them as the second argument to the
$url = route('routeName', ['id' => 1]);
+
+#### `secure_url()` {#collection-method}
+
+The `secure_url` function generates a fully qualified HTTPS URL to the given path:
+
+ echo secure_url('user/profile');
+
+ echo secure_url('user/profile', [1]);
+
#### `url()` {#collection-method}
@@ -777,6 +802,21 @@ The `bcrypt` function hashes the given value using Bcrypt. You may use it as an
$password = bcrypt('my-secret-password');
+
+#### `cache()` {#collection-method}
+
+The `cache` function may be used to get values from the cache. If the given key does not exist in the cache, an optional default value will be returned:
+
+ $value = cache('key');
+
+ $value = cache('key', 'default');
+
+You may add items to the cache by passing an array of key / value pairs to the function. You should also pass the number of minutes or duration the cached value should be considered valid:
+
+ cache(['key' => 'value'], 5);
+
+ cache(['key' => 'value'], Carbon::now()->addSeconds(10));
+
#### `collect()` {#collection-method}
@@ -814,10 +854,12 @@ The `csrf_token` function retrieves the value of the current CSRF token:
#### `dd()` {#collection-method}
-The `dd` function dumps the given variable and ends execution of the script:
+The `dd` function dumps the given variables and ends execution of the script:
dd($value);
+ dd($value1, $value2, $value3, ...);
+
If you do not want to halt the execution of your script, use the `dump` function instead:
dump($value);
@@ -853,6 +895,32 @@ The `factory` function creates a model factory builder for a given class, name,
$user = factory(App\User::class)->make();
+
+#### `info()` {#collection-method}
+
+The `info` function will write information to the log:
+
+ info('Some helpful information!');
+
+An array of contextual data may also be passed to the function:
+
+ info('User login attempt failed.', ['id' => $user->id]);
+
+
+#### `logger()` {#collection-method}
+
+The `logger` function can be used to write a `debug` level message to the log:
+
+ logger('Debug message');
+
+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:
+
+ logger()->error('You are not allowed here.');
+
#### `method_field()` {#collection-method}
@@ -898,6 +966,15 @@ The `response` function creates a [response](/docs/{{version}}/responses) instan
return response()->json(['foo' => 'bar'], 200, $headers);
+
+#### `retry()` {#collection-method}
+
+The `retry` function attempts to execute the given callback until the given maximum attempt threshold is met. If the callback does not throw an exception, it's return value will be returned. If the callback throws an exception, it will automatically be retried. If the maximum attempt count is exceeded, the exception will be thrown:
+
+ return retry(5, function () {
+ // Attempt 5 times while resting 100ms in between attempts...
+ }, 100);
+
#### `session()` {#collection-method}
@@ -920,7 +997,9 @@ The session store will be returned if no value is passed to the function:
The `value` function's behavior will simply return the value it is given. However, if you pass a `Closure` to the function, the `Closure` will be executed then its result will be returned:
- $value = value(function() { return 'bar'; });
+ $value = value(function () {
+ return 'bar';
+ });
#### `view()` {#collection-method}
diff --git a/homestead.md b/homestead.md
index dd2207f..35874b1 100644
--- a/homestead.md
+++ b/homestead.md
@@ -15,15 +15,17 @@
- [Configuring Cron Schedules](#configuring-cron-schedules)
- [Ports](#ports)
- [Network Interfaces](#network-interfaces)
+- [Updating Homestead](#updating-homestead)
+- [Old Versions](#old-versions)
## Introduction
-Laravel strives to make the entire PHP development experience delightful, including your local development environment. [Vagrant](http://vagrantup.com) provides a simple, elegant way to manage and provision Virtual Machines.
+Laravel strives to make the entire PHP development experience delightful, including your local development environment. [Vagrant](https://www.vagrantup.com) provides a simple, elegant way to manage and provision Virtual Machines.
-Laravel Homestead is an official, pre-packaged Vagrant box that provides you a wonderful development environment without requiring you to install PHP, HHVM, a web server, and any other server software on your local machine. No more worrying about messing up your operating system! Vagrant boxes are completely disposable. If something goes wrong, you can destroy and re-create the box in minutes!
+Laravel Homestead is an official, pre-packaged Vagrant box that provides you a wonderful development environment without requiring you to install PHP, a web server, and any other server software on your local machine. No more worrying about messing up your operating system! Vagrant boxes are completely disposable. If something goes wrong, you can destroy and re-create the box in minutes!
-Homestead runs on any Windows, Mac, or Linux system, and includes the Nginx web server, PHP 7.0, MySQL, Postgres, Redis, Memcached, Node, and all of the other goodies you need to develop amazing Laravel applications.
+Homestead runs on any Windows, Mac, or Linux system, and includes the Nginx web server, PHP 7.1, MySQL, Postgres, Redis, Memcached, Node, and all of the other goodies you need to develop amazing Laravel applications.
> {note} If you are using Windows, you may need to enable hardware virtualization (VT-x). It can usually be enabled via your BIOS. If you are using Hyper-V on a UEFI system you may additionally need to disable Hyper-V in order to access VT-x.
@@ -32,15 +34,14 @@ Homestead runs on any Windows, Mac, or Linux system, and includes the Nginx web
- Ubuntu 16.04
- Git
-- PHP 7.0
-- HHVM
+- PHP 7.1
- Nginx
- MySQL
- MariaDB
- Sqlite3
- Postgres
- Composer
-- Node (With PM2, Bower, Grunt, and Gulp)
+- Node (With Yarn, PM2, Bower, Grunt, and Gulp)
- Redis
- Memcached
- Beanstalkd
@@ -51,9 +52,11 @@ Homestead runs on any Windows, Mac, or Linux system, and includes the Nginx web
### First Steps
-Before launching your Homestead environment, you must install [VirtualBox 5.x](https://www.virtualbox.org/wiki/Downloads) or [VMWare](http://www.vmware.com) as well as [Vagrant](http://www.vagrantup.com/downloads.html). All of these software packages provide easy-to-use visual installers for all popular operating systems.
+Before launching your Homestead environment, you must install [VirtualBox 5.1](https://www.virtualbox.org/wiki/Downloads), [VMWare](https://www.vmware.com), or [Parallels](http://www.parallels.com/products/desktop/) as well as [Vagrant](https://www.vagrantup.com/downloads.html). All of these software packages provide easy-to-use visual installers for all popular operating systems.
-To use the VMware provider, you will need to purchase both VMware Fusion / Workstation and the [VMware Vagrant plug-in](http://www.vagrantup.com/vmware). Though it is not free, VMware can provide faster shared folder performance out of the box.
+To use the VMware provider, you will need to purchase both VMware Fusion / Workstation and the [VMware Vagrant plug-in](https://www.vagrantup.com/vmware). Though it is not free, VMware can provide faster shared folder performance out of the box.
+
+To use the Parallels provider, you will need to install [Parallels Vagrant plug-in](https://github.com/Parallels/vagrant-parallels). It is free of charge.
#### Installing The Homestead Vagrant Box
@@ -73,14 +76,18 @@ You may install Homestead by simply cloning the repository. Consider cloning the
Once you have cloned the Homestead repository, run the `bash init.sh` command from the Homestead directory to create the `Homestead.yaml` configuration file. The `Homestead.yaml` file will be placed in the `~/.homestead` hidden directory:
+ // Mac / Linux...
bash init.sh
+ // Windows...
+ init.bat
+
### Configuring Homestead
#### Setting Your Provider
-The `provider` key in your `~/.homestead/Homestead.yaml` file indicates which Vagrant provider should be used: `virtualbox`, `vmware_fusion`, or `vmware_workstation`. You may set this to the provider you prefer:
+The `provider` key in your `~/.homestead/Homestead.yaml` file indicates which Vagrant provider should be used: `virtualbox`, `vmware_fusion`, `vmware_workstation`, or `parallels`. You may set this to the provider you prefer:
provider: virtualbox
@@ -92,27 +99,31 @@ The `folders` property of the `Homestead.yaml` file lists all of the folders you
- map: ~/Code
to: /home/vagrant/Code
-To enable [NFS](http://docs.vagrantup.com/v2/synced-folders/nfs.html), just add a simple flag to your synced folder configuration:
+To enable [NFS](https://www.vagrantup.com/docs/synced-folders/nfs.html), just add a simple flag to your synced folder configuration:
folders:
- map: ~/Code
to: /home/vagrant/Code
type: "nfs"
-#### Configuring Nginx Sites
+You may also pass any options supported by Vagrant's [Synced Folders](https://www.vagrantup.com/docs/synced-folders/basic_usage.html) by listing them under the `options` key:
-Not familiar with Nginx? No problem. The `sites` property allows you to easily map a "domain" to a folder on your Homestead environment. A sample site configuration is included in the `Homestead.yaml` file. Again, you may add as many sites to your Homestead environment as necessary. Homestead can serve as a convenient, virtualized environment for every Laravel project you are working on:
+ folders:
+ - map: ~/Code
+ to: /home/vagrant/Code
+ type: "rsync"
+ options:
+ rsync__args: ["--verbose", "--archive", "--delete", "-zz"]
+ rsync__exclude: ["node_modules"]
- sites:
- - map: homestead.app
- to: /home/vagrant/Code/Laravel/public
-You can make any Homestead site use [HHVM](http://hhvm.com) by setting the `hhvm` option to `true`:
+#### Configuring Nginx Sites
+
+Not familiar with Nginx? No problem. The `sites` property allows you to easily map a "domain" to a folder on your Homestead environment. A sample site configuration is included in the `Homestead.yaml` file. Again, you may add as many sites to your Homestead environment as necessary. Homestead can serve as a convenient, virtualized environment for every Laravel project you are working on:
sites:
- map: homestead.app
to: /home/vagrant/Code/Laravel/public
- hhvm: true
If you change the `sites` property after provisioning the Homestead box, you should re-run `vagrant reload --provision` to update the Nginx configuration on the virtual machine.
@@ -150,7 +161,7 @@ Mac / Linux:
Windows:
- vendor\\bin\\homestead make
+ vendor\\bin\\homestead make
Next, run the `vagrant up` command in your terminal and access your project at `http://homestead.app` in your browser. Remember, you will still need to add an `/etc/hosts` file entry for `homestead.app` or the domain of your choice.
@@ -172,7 +183,9 @@ If you prefer to use MariaDB instead of MySQL, you may add the `mariadb` option
### Accessing Homestead Globally
-Sometimes you may want to `vagrant up` your Homestead machine from anywhere on your filesystem. You can do this by adding a simple Bash function to your Bash profile. This function will allow you to run any Vagrant command from anywhere on your system and will automatically point that command to your Homestead installation:
+Sometimes you may want to `vagrant up` your Homestead machine from anywhere on your filesystem. You can do this on Mac / Linux systems by adding a Bash function to your Bash profile. On Windows, you may accomplish this by adding a "batch" file to your `PATH`. This scripts will allow you to run any Vagrant command from anywhere on your system and will automatically point that command to your Homestead installation:
+
+#### Linux
function homestead() {
( cd ~/Homestead && vagrant $* )
@@ -180,6 +193,23 @@ Sometimes you may want to `vagrant up` your Homestead machine from anywhere on y
Make sure to tweak the `~/Homestead` path in the function to the location of your actual Homestead installation. Once the function is installed, you may run commands like `homestead up` or `homestead ssh` from anywhere on your system.
+#### Windows
+
+Create a `homestead.bat` batch file anywhere on your machine with the following contents:
+
+ @echo off
+
+ set cwd=%cd%
+ set homesteadVagrant=C:\Homestead
+
+ cd /d %homesteadVagrant% && vagrant %*
+ cd /d %cwd%
+
+ set cwd=
+ set homesteadVagrant=
+
+Make sure to tweak the example `C:\Homestead` path in the script to the actual location of your Homestead installation. After creating the file, add the file location to your `PATH`. You may then run commands like `homestead up` or `homestead ssh` from anywhere on your system.
+
### Connecting Via SSH
@@ -192,19 +222,32 @@ But, since you will probably need to SSH into your Homestead machine frequently,
A `homestead` database is configured for both MySQL and Postgres out of the box. For even more convenience, Laravel's `.env` file configures the framework to use this database out of the box.
-To connect to your MySQL or Postgres database from your host machine via Navicat or Sequel Pro, you should connect to `127.0.0.1` and port `33060` (MySQL) or `54320` (Postgres). The username and password for both databases is `homestead` / `secret`.
+To connect to your MySQL or Postgres database from your host machine's database client, you should connect to `127.0.0.1` and port `33060` (MySQL) or `54320` (Postgres). The username and password for both databases is `homestead` / `secret`.
> {note} You should only use these non-standard ports when connecting to the databases from your host machine. You will use the default 3306 and 5432 ports in your Laravel database configuration file since Laravel is running _within_ the virtual machine.
### Adding Additional Sites
-Once your Homestead environment is provisioned and running, you may want to add additional Nginx sites for your Laravel applications. You can run as many Laravel installations as you wish on a single Homestead environment. To add an additional site, simply add the site to your `~/.homestead/Homestead.yaml` file and then run the `vagrant provision` terminal command from your Homestead directory.
+Once your Homestead environment is provisioned and running, you may want to add additional Nginx sites for your Laravel applications. You can run as many Laravel installations as you wish on a single Homestead environment. To add an additional site, simply add the site to your `~/.homestead/Homestead.yaml` file:
+
+ sites:
+ - map: homestead.app
+ to: /home/vagrant/Code/Laravel/public
+ - map: another.app
+ to: /home/vagrant/Code/another/public
+
+If Vagrant is not automatically managing your "hosts" file, you may need to add the new site to that file as well:
+
+ 192.168.10.10 homestead.app
+ 192.168.10.10 another.app
+
+Once the site has been added, run the `vagrant reload --provision` command from your Homestead directory.
### Configuring Cron Schedules
-Laravel provides a convenient way to [schedule Cron jobs](/docs/{{version}}/scheduling) by scheduling a single `schedule:run` Artisan command to be run every minute. The `schedule:run` command will examine the job scheduled defined in your `App\Console\Kernel` class to determine which jobs should be run.
+Laravel provides a convenient way to [schedule Cron jobs](/docs/{{version}}/scheduling) by scheduling a single `schedule:run` Artisan command to be run every minute. The `schedule:run` command will examine the job schedule defined in your `App\Console\Kernel` class to determine which jobs should be run.
If you would like the `schedule:run` command to be run for a Homestead site, you may set the `schedule` option to `true` when defining the site:
@@ -258,3 +301,39 @@ To enable [DHCP](https://www.vagrantup.com/docs/networking/public_network.html),
networks:
- type: "public_network"
bridge: "en1: Wi-Fi (AirPort)"
+
+
+## Updating Homestead
+
+You can update Homestead in two simple steps. First, you should update the Vagrant box using the `vagrant box update` command:
+
+ vagrant box update
+
+Next, you need to update the Homestead source code. If you cloned the repository you can simply `git pull origin master` at the location you originally cloned the repository.
+
+If you have installed Homestead via your project's `composer.json` file, you should ensure your `composer.json` file contains `"laravel/homestead": "^4"` and update your dependencies:
+
+ composer update
+
+
+## Old Versions
+
+You can easily override the version of the box that Homestead uses by adding the following line to your `Homestead.yaml` file:
+
+ version: 0.6.0
+
+An example:
+
+ box: laravel/homestead
+ version: 0.6.0
+ ip: "192.168.20.20"
+ memory: 2048
+ cpus: 4
+ provider: virtualbox
+
+When you use an older version of the Homestead box you need to match that with a compatible version of the Homestead source code. Below is a chart which shows the supported box versions, which version of Homestead source code to use, and the version of PHP provided:
+
+| | Homestead Version | Box Version |
+|---|---|---|
+| PHP 7.0 | 3.1.0 | 0.6.0 |
+| PHP 7.1 | 4.0.0 | 1.0.0 |
diff --git a/http-tests.md b/http-tests.md
new file mode 100644
index 0000000..33ff0af
--- /dev/null
+++ b/http-tests.md
@@ -0,0 +1,145 @@
+# HTTP Tests
+
+- [Introduction](#introduction)
+- [Session / Authentication](#session-and-authentication)
+- [Testing JSON APIs](#testing-json-apis)
+- [Available Assertions](#available-assertions)
+
+
+## Introduction
+
+Laravel provides a very fluent API for making HTTP requests to your application and examining the output. For example, take a look at the test defined below:
+
+ get('/');
+
+ $response->assertStatus(200);
+ }
+ }
+
+The `get` method makes a `GET` request into the application, while the `assertStatus` method asserts that the returned response should have the given HTTP status code. In addition to this simple assertion, Laravel also contains a variety of assertions for inspecting the response headers, content, JSON structure, and more.
+
+
+### Session / Authentication
+
+Laravel provides several helpers for working with the session during HTTP testing. First, you may set the session data to a given array using the `withSession` method. This is useful for loading the session with data before issuing a request to your application:
+
+ withSession(['foo' => 'bar'])
+ ->get('/');
+ }
+ }
+
+Of course, one common use of the session is for maintaining state for the authenticated user. The `actingAs` helper method provides a simple way to authenticate a given user as the current user. For example, we may use a [model factory](/docs/{{version}}/database-testing#writing-factories) to generate and authenticate a user:
+
+ create();
+
+ $response = $this->actingAs($user)
+ ->withSession(['foo' => 'bar'])
+ ->get('/')
+ }
+ }
+
+You may also specify which guard should be used to authenticate the given user by passing the guard name as the second argument to the `actingAs` method:
+
+ $this->actingAs($user, 'api')
+
+
+### Testing JSON APIs
+
+Laravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `get`, `post`, `put`, `patch`, and `delete` methods may be used to issue requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/user` and assert that the expected data was returned:
+
+ json('POST', '/user', ['name' => 'Sally']);
+
+ $response
+ ->assertStatus(200)
+ ->assertJson([
+ 'created' => true,
+ ]);
+ }
+ }
+
+> {tip} The `assertJson` method converts the response to an array and utilizes `PHPUnit::assertArraySubset` to verify that the given fragment 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.
+
+
+### Verifying Exact Match
+
+If you would like to verify that the given array is an **exact** match for the JSON returned by the application, you should use the `assertExactJson` method:
+
+ json('POST', '/user', ['name' => 'Sally']);
+
+ $response
+ ->assertStatus(200)
+ ->assertExactJson([
+ 'created' => true,
+ ]);
+ }
+ }
+
+
+### Available Assertions
+
+Laravel provides a variety of custom assertion methods for your [PHPUnit](https://phpunit.de/) tests. These assertions may be accessed on the response that is returned from the `json`, `get`, `post`, `put`, and `delete` test methods:
+
+Method | Description
+------------- | -------------
+`$response->assertStatus($code);` | Assert that the response has a given code.
+`$response->assertRedirect($uri);` | Assert that the response is a redirect to a given URI.
+`$response->assertHeader($headerName, $value = null);` | Assert that the given header is present on the response.
+`$response->assertCookie($cookieName, $value = null);` | Assert that the response contains the given cookie.
+`$response->assertPlainCookie($cookieName, $value = null);` | Assert that the response contains the given cookie (unencrypted).
+`$response->assertSessionHas($key, $value = null);` | Assert that the session contains the given piece of data.
+`$response->assertSessionMissing($key);` | Assert that the session does not contain the given key.
+`$response->assertJson(array $data);` | Assert that the response contains the given JSON data.
+`$response->assertExactJson(array $data);` | Assert that the response contains an exact match of the given JSON data.
+`$response->assertViewHas($key, $value = null);` | Assert that the response view was given a piece of data.
diff --git a/installation.md b/installation.md
index bec3277..db68b6d 100644
--- a/installation.md
+++ b/installation.md
@@ -4,10 +4,14 @@
- [Server Requirements](#server-requirements)
- [Installing Laravel](#installing-laravel)
- [Configuration](#configuration)
+- [Web Server Configuration](#web-server-configuration)
+ - [Pretty URLs](#pretty-urls)
## Installation
+> {video} Are you a visual learner? Laracasts provides a [free, thorough introduction to Laravel](https://laracasts.com/series/laravel-from-scratch-2017) for newcomers to the framework. It's a great place to start your journey.
+
### Server Requirements
@@ -21,12 +25,13 @@ However, if you are not using Homestead, you will need to make sure your server
- PDO PHP Extension
- Mbstring PHP Extension
- Tokenizer PHP Extension
+- XML PHP Extension
### Installing Laravel
-Laravel utilizes [Composer](http://getcomposer.org) to manage its dependencies. So, before using Laravel, make sure you have Composer installed on your machine.
+Laravel utilizes [Composer](https://getcomposer.org) to manage its dependencies. So, before using Laravel, make sure you have Composer installed on your machine.
#### Via Laravel Installer
@@ -34,7 +39,7 @@ First, download the Laravel installer using Composer:
composer global require "laravel/installer"
-Make sure to place the `~/.composer/vendor/bin` directory (or the equivalent directory for your OS) in your $PATH so the `laravel` executable can be located by your system.
+Make sure to place the `$HOME/.composer/vendor/bin` directory (or the equivalent directory for your OS) in your $PATH so the `laravel` executable can be located by your system.
Once installed, the `laravel new` command will create a fresh Laravel installation in the directory you specify. For instance, `laravel new blog` will create a directory named `blog` containing a fresh Laravel installation with all of Laravel's dependencies already installed:
@@ -46,12 +51,20 @@ Alternatively, you may also install Laravel by issuing the Composer `create-proj
composer create-project --prefer-dist laravel/laravel blog
+#### Local Development Server
+
+If you have PHP installed locally and you would like to use PHP's built-in development server to serve your application, you may use the `serve` Artisan command. This command will start a development server at `http://localhost:8000`:
+
+ php artisan serve
+
+Of course, more robust local development options are available via [Homestead](/docs/{{version}}/homestead) and [Valet](/docs/{{version}}/valet).
+
### Configuration
#### Public Directory
-After installing Laravel, you should configure your your web server's document / web root to be the `public` directory. The `index.php` in this directory serves as the front controller for all HTTP requests entering your application.
+After installing Laravel, you should configure your web server's document / web root to be the `public` directory. The `index.php` in this directory serves as the front controller for all HTTP requests entering your application.
#### Configuration Files
@@ -79,4 +92,31 @@ You may also want to configure a few additional components of Laravel, such as:
- [Session](/docs/{{version}}/session#configuration)
-Once Laravel is installed, you should also [configure your local environment](/docs/{{version}}/configuration#environment-configuration).
+
+## Web Server Configuration
+
+
+### Pretty URLs
+
+#### Apache
+
+Laravel includes a `public/.htaccess` file that is used to provide URLs without the `index.php` front controller in the path. Before serving Laravel with Apache, be sure to enable the `mod_rewrite` module so the `.htaccess` file will be honored by the server.
+
+If the `.htaccess` file that ships with Laravel does not work with your Apache installation, try this alternative:
+
+ Options +FollowSymLinks
+ RewriteEngine On
+
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [L]
+
+#### Nginx
+
+If you are using Nginx, the following directive in your site configuration will direct all requests to the `index.php` front controller:
+
+ location / {
+ try_files $uri $uri/ /index.php?$query_string;
+ }
+
+Of course, when using [Homestead](/docs/{{version}}/homestead) or [Valet](/docs/{{version}}/valet), pretty URLs will be automatically configured.
diff --git a/lifecycle.md b/lifecycle.md
index a4adf21..27e0da9 100644
--- a/lifecycle.md
+++ b/lifecycle.md
@@ -9,9 +9,7 @@
When using any tool in the "real world", you feel more confident if you understand how that tool works. Application development is no different. When you understand how your development tools function, you feel more comfortable and confident using them.
-The goal of this document is to give you a good, high-level overview of how the Laravel framework "works". By getting to know the overall framework better, everything feels less "magical" and you will be more confident building your applications.
-
-If you don't understand all of the terms right away, don't lose heart! Just try to get a basic grasp of what is going on, and your knowledge will grow as you explore other sections of the documentation.
+The goal of this document is to give you a good, high-level overview of how the Laravel framework works. By getting to know the overall framework better, everything feels less "magical" and you will be more confident building your applications. If you don't understand all of the terms right away, don't lose heart! Just try to get a basic grasp of what is going on, and your knowledge will grow as you explore other sections of the documentation.
## Lifecycle Overview
@@ -26,9 +24,9 @@ The `index.php` file loads the Composer generated autoloader definition, and the
Next, the incoming request is sent to either the HTTP kernel or the console kernel, depending on the type of request that is entering the application. These two kernels serve as the central location that all requests flow through. For now, let's just focus on the HTTP kernel, which is located in `app/Http/Kernel.php`.
-The HTTP kernel extends the `Illuminate\Foundation\Http\Kernel` class, which defines an array of `bootstrappers` that will be run before the request is executed. These bootstrappers configure error handling, configure logging, [detect the application environment](/docs/{{version}}/installation#environment-configuration), and perform other tasks that need to be done before the request is actually handled.
+The HTTP kernel extends the `Illuminate\Foundation\Http\Kernel` class, which defines an array of `bootstrappers` that will be run before the request is executed. These bootstrappers configure error handling, configure logging, [detect the application environment](/docs/{{version}}/configuration#environment-configuration), and perform other tasks that need to be done before the request is actually handled.
-The HTTP kernel also defines a list of HTTP [middleware](/docs/{{version}}/middleware) that all requests must pass through before being handled by the application. These middleware handle reading and writing the [HTTP session](/docs/{{version}}/session), determine if the application is in maintenance mode, [verifying the CSRF token](/docs/{{version}}/routing#csrf-protection), and more.
+The HTTP kernel also defines a list of HTTP [middleware](/docs/{{version}}/middleware) that all requests must pass through before being handled by the application. These middleware handle reading and writing the [HTTP session](/docs/{{version}}/session), determining if the application is in maintenance mode, [verifying the CSRF token](/docs/{{version}}/csrf), and more.
The method signature for the HTTP kernel's `handle` method is quite simple: receive a `Request` and return a `Response`. Think of the Kernel as being a big black box that represents your entire application. Feed it HTTP requests and it will return HTTP responses.
diff --git a/localization.md b/localization.md
index e8b8ef7..075487c 100644
--- a/localization.md
+++ b/localization.md
@@ -1,8 +1,11 @@
# Localization
- [Introduction](#introduction)
-- [Retrieving Language Lines](#retrieving-language-lines)
- - [Replacing Parameters In Language Lines](#replacing-parameters-in-language-lines)
+- [Defining Translation Strings](#defining-translation-strings)
+ - [Using Short Keys](#using-short-keys)
+ - [Using Translation Strings As Keys](#using-translation-strings-as-keys)
+- [Retrieving Translation Strings](#retrieving-translation-strings)
+ - [Replacing Parameters In Translation Strings](#replacing-parameters-in-translation-strings)
- [Pluralization](#pluralization)
- [Overriding Package Language Files](#overriding-package-language-files)
@@ -36,7 +39,7 @@ The default language for your application is stored in the `config/app.php` conf
//
});
-You may configure a "fallback language", which will be used when the active language does not contain a given language line. Like the default language, the fallback language is also configured in the `config/app.php` configuration file:
+You may configure a "fallback language", which will be used when the active language does not contain a given translation string. Like the default language, the fallback language is also configured in the `config/app.php` configuration file:
'fallback_locale' => 'en',
@@ -50,31 +53,69 @@ You may use the `getLocale` and `isLocale` methods on the `App` facade to determ
//
}
-
-## Retrieving Language Lines
+
+## Defining Translation Strings
-You may retrieve lines from language files using the `trans` helper function. The `trans` method accepts the file and key of the language line as its first argument. For example, let's retrieve the `welcome` language line from the `resources/lang/messages.php` language file:
+
+### Using Short Keys
- echo trans('messages.welcome');
+Typically, translation strings are stored in files within the `resources/lang` directory. Within this directory there should be a subdirectory for each language supported by the application:
-Of course if you are using the [Blade templating engine](/docs/{{version}}/blade), you may use the `{{ }}` syntax to echo the language line or use the `@lang` directive:
+ /resources
+ /lang
+ /en
+ messages.php
+ /es
+ messages.php
+
+All language files simply return an array of keyed strings. For example:
+
+ 'Welcome to our application'
+ ];
+
+
+### Using Translation Strings As Keys
+
+For applications with heavy translation requirements, defining every string with a "short key" can become quickly confusing when referencing them in your views. For this reason, Laravel also provides support for defining translation strings using the "default" translation of the string as the key.
+
+Translation files that use translation strings as keys are stored as JSON files in the `resources/lang` directory. For example, if your application has a Spanish translation, you should create a `resources/lang/es.json` file:
+
+ {
+ "I love programming.": "Me encanta la programación."
+ }
+
+
+## Retrieving Translation Strings
+
+You may retrieve lines from language files using the `__` helper function. The `__` method accepts the file and key of the translation string as its first argument. For example, let's retrieve the `welcome` translation string from the `resources/lang/messages.php` language file:
+
+ echo __('messages.welcome');
+
+ echo __('I love programming.');
+
+Of course if you are using the [Blade templating engine](/docs/{{version}}/blade), you may use the `{{ }}` syntax to echo the translation string or use the `@lang` directive:
+
+ {{ __('messages.welcome') }}
@lang('messages.welcome')
-If the specified language line does not exist, the `trans` function will simply return the language line key. So, using the example above, the `trans` function would return `messages.welcome` if the language line does not exist.
+If the specified translation string does not exist, the `__` function will simply return the translation string key. So, using the example above, the `__` function would return `messages.welcome` if the translation string does not exist.
-
-### Replacing Parameters In Language Lines
+
+### Replacing Parameters In Translation Strings
-If you wish, you may define place-holders in your language lines. All place-holders are prefixed with a `:`. For example, you may define a welcome message with a place-holder name:
+If you wish, you may define place-holders in your translation strings. All place-holders are prefixed with a `:`. For example, you may define a welcome message with a place-holder name:
'welcome' => 'Welcome, :name',
-To replace the place-holders when retrieving a language line, pass an array of replacements as the second argument to the `trans` function:
+To replace the place-holders when retrieving a translation string, pass an array of replacements as the second argument to the `__` function:
- echo trans('messages.welcome', ['name' => 'dayle']);
+ echo __('messages.welcome', ['name' => 'dayle']);
If your place-holder contains all capital letters, or only has its first letter capitalized, the translated value will be capitalized accordingly:
@@ -89,17 +130,17 @@ Pluralization is a complex problem, as different languages have a variety of com
'apples' => 'There is one apple|There are many apples',
-After defining a language line that has pluralization options, you may use the `trans_choice` function to retrieve the line for a given "count". In this example, since the count is greater than one, the plural form of the language line is returned:
+You may even create more complex pluralization rules which specify translation strings for multiple number ranges:
- echo trans_choice('messages.apples', 10);
+ 'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many',
-Since the Laravel translator is powered by the Symfony Translation component, you may create even more complex pluralization rules which specify language lines for multiple number ranges:
+After defining a translation string that has pluralization options, you may use the `trans_choice` function to retrieve the line for a given "count". In this example, since the count is greater than one, the plural form of the translation string is returned:
- 'apples' => '{0} There are none|[1,19] There are some|[20,Inf] There are many',
+ echo trans_choice('messages.apples', 10);
## Overriding Package Language Files
Some packages may ship with their own language files. Instead of changing the package's core files to tweak these lines, you may override them by placing files in the `resources/lang/vendor/{package}/{locale}` directory.
-So, for example, if you need to override the English language lines in `messages.php` for a package named `skyrim/hearthfire`, you should place a language file at: `resources/lang/vendor/hearthfire/en/messages.php`. Within this file, you should only define the language lines you wish to override. Any language lines you don't override will still be loaded from the package's original language files.
+So, for example, if you need to override the English translation strings in `messages.php` for a package named `skyrim/hearthfire`, you should place a language file at: `resources/lang/vendor/hearthfire/en/messages.php`. Within this file, you should only define the translation strings you wish to override. Any translation strings you don't override will still be loaded from the package's original language files.
diff --git a/mail.md b/mail.md
index 3e0b21b..d0968d7 100644
--- a/mail.md
+++ b/mail.md
@@ -9,6 +9,10 @@
- [View Data](#view-data)
- [Attachments](#attachments)
- [Inline Attachments](#inline-attachments)
+- [Markdown Mailables](#markdown-mailables)
+ - [Generating Markdown Mailables](#generating-markdown-mailables)
+ - [Writing Markdown Messages](#writing-markdown-messages)
+ - [Customizing The Components](#customizing-the-components)
- [Sending Mail](#sending-mail)
- [Queueing Mail](#queueing-mail)
- [Mail & Local Development](#mail-and-local-development)
@@ -304,6 +308,91 @@ If you already have a raw data string you wish to embed into an email template,
@@ -18,7 +18,7 @@ Views contain the HTML served by your application and separate your controller /