Here’s why you should sign up for our app: It’s Great.
@include(‘sign-up-button’, [‘text’ => ‘See just how great it is’])وبلاگ
Here’s why you should sign up for our app: It’s Great.
@include(‘sign-up-button’, [‘text’ => ‘See just how great it is’])The password you have provided is not valid. Here are the rules for valid passwords: […]
‘ ]) This is too much for this variable, and it’s the perfect fit for a component. Components with slots are view partials that are explicitly designed to have big chunks (“slots”) that are meant to get content from the including template. Take a look at Example 4-15 to see how to refactor Example 4-14 with components and slots. Example 4-15. A modal as a more appropriate component with slotsThe password you have provided is not valid. Here are the rules for valid passwords: […]
@endcomponent As you can see in Example 4-15, the @component directive allows us to pull our HTML out of a cramped variable string and back into the template space. The $slot variable in our component template receives whatever content is passed in the @compo nent directive. Multiple slots The method we used in Example 4-15 is called the “default” slot; whatever you pass in between @component and @endcomponent is passed to the $slot variable. But you can also have more than just the default slot. Let’s imagine a modal with a title, like in Example 4-16. Example 4-16. A modal view partial with two variablesThe password you have provided is not valid. Here are the rules for valid passwords: […]
@endcomponent 74 | Chapter 4: Blade Templating And if you have other variables in your view that don’t make sense as a slot, you can still pass an array of content as the second parameter to @component, just like you can with @include. Take a look at Example 4-18. Example 4-18. Passing data to a component without slots @component(‘partials.modal’, [‘class’ => ‘danger’]) … @endcomponent Aliasing a component to be a directive There’s a clever trick you can use to make your components even easier to call: alias‐ ing. Simply call Blade::component() on the Blade facade—the most common loca‐ tion is the boot() method of the AppServiceProvider—and pass it first the location of the component and second the name of your desired directive, as shown in Example 4-19. Example 4-19. Aliasing a component to be a directive // AppServiceProvider@boot Blade::component(‘partials.modal’, ‘modal’); @modal Modal content here @endmodal Importing Facades This is our first time working with a facade in a namespaced class. We’ll cover them in more depth later, but just know that if you use facades in namespaced classes, which is most classes in recent ver‐ sions of Laravel, you might find errors showing that the facade can‐ not be found. This is because facades are just normal classes with normal namespaces, but Laravel does a bit of trickery to make them available from the root namespace. So, in Example 4-19, we’d need to import the Illuminate\Support \Facades\Blade facade at the top of the file. View Composers and Service Injection As we covered in Chapter 3, it’s simple to pass data to our views from the route defi‐ nition (see Example 4-20). View Composers and Service Injection | 75 Example 4-20. Reminder of how to pass data to views Route::get(‘passing-data-to-views’, function () { return view(‘dashboard’) ->with(‘key’, ‘value’); }); There may be times, however, when you find yourself passing the same data over and over to multiple views. Or you might find yourself using a header partial or some‐ thing similar that requires some data; will you have to pass that data in from every route definition that might ever load that header partial? Binding Data to Views Using View Composers Thankfully, there’s a simpler way. The solution is called a view composer, and it allows you to define that any time a particular view loads, it should have certain data passed to it—without the route definition having to pass that data in explicitly. Let’s say you have a sidebar on every page, which is defined in a partial named partials.sidebar (resources/views/partials/sidebar.blade.php) and then included on every page. This sidebar shows a list of the last seven posts that were published on your site. If it’s on every page, every route definition would normally have to grab that list and pass it in, like in Example 4-21. Example 4-21. Passing sidebar data in from every route Route::get(‘home’, function () { return view(‘home’) ->with(‘posts’, Post::recent()); }); Route::get(‘about’, function () { return view(‘about’) ->with(‘posts’, Post::recent()); }); That could get annoying quickly. Instead, we’re going to use view composers to “share” that variable with a prescribed set of views. We can do this a few ways, so let’s start simple and move up. Sharing a variable globally First, the simplest option: just globally “share” a variable with every view in your application like in Example 4-22. 76 | Chapter 4: Blade Templating Example 4-22. Sharing a variable globally // Some service provider public function boot() { … view()->share(‘recentPosts’, Post::recent()); } If you want to use view()->share(), the best place would be the boot() method of a service provider so that the binding runs on every page load. You can create a custom ViewComposerServiceProvider (see Chapter 11 for more about service providers), but for now just put it in App\Providers\AppServiceProvider in the boot() method. Using view()->share() makes the variable accessible to every view in the entire application, however, so it might be overkill. View-scoped view composers with closures The next option is to use a closure-based view composer to share variables with a sin‐ gle view, like in Example 4-23. Example 4-23. Creating a closure-based view composer view()->composer(‘partials.sidebar’, function ($view) { $view->with(‘recentPosts’, Post::recent()); }); As you can see, we’ve defined the name of the view we want it shared with in the first parameter (partials.sidebar) and then passed a closure to the second parameter; in the closure we’ve used $view->with() to share a variable, but only with a specific view. View Composers for Multiple Views Anywhere a view composer is binding to a particular view (like in Example 4-23, which binds to partials.sidebar), you can pass an array of view names instead to bind to multiple views. You can also use an asterisk in the view path, as in partials.* or tasks.*: view()->composer( [‘partials.header’, ‘partials.footer’], function () { $view->with(‘recentPosts’, Post::recent()); } ); View Composers and Service Injection | 77 view()->composer(‘partials.*’, function () { $view->with(‘recentPosts’, Post::recent()); }); View-scoped view composers with classes Finally, the most flexible but also most complex option is to create a dedicated class for your view composer. First, let’s create the view composer class. There’s no formally defined place for view composers to live, but the docs recommend App\Http\ViewComposers. So, let’s create App\Http\ViewComposers\RecentPostsComposer like in Example 4-24. Example 4-24. A view composer with(‘recentPosts’, Post::recent()); } } As you can see, when this composer is called, it runs the compose() method, in which we bind the posts variable to the result of running the Post model’s recent() method. Like the other methods of sharing variables, this view composer needs to have a bind‐ ing somewhere. Again, you’d likely create a custom ViewComposerServiceProvider, but for now, as seen in Example 4-25, we’ll just put it in the boot() method of App \Providers\AppServiceProvider. Example 4-25. Registering a view composer in AppServiceProvider public function boot() { view()->composer( ‘partials.sidebar’, \App\Http\ViewComposers\RecentPostsComposer::class 78 | Chapter 4: Blade Templating ); } Note that this binding is the same as a closure-based view composer, but instead of passing a closure, we’re passing the class name of our view composer. Now, every time Blade renders the partials.sidebar view, it’ll automatically run our provider and pass the view a recentPosts variable set to the results of the recent() method on our Post model. Blade Service Injection There are three primary types of data we’re most likely to inject into a view: collec‐ tions of data to iterate over, single objects that we’re displaying on the page, and serv‐ ices that generate data or views. With a service, the pattern will most likely look like Example 4-26, where we inject an instance of our analytics service into the route definition by typehinting it in the route’s method signature, and then pass it into the view. Example 4-26. Injecting services into a view via the route denition constructor Route::get(‘backend/sales’, function (AnalyticsService $analytics) { return view(‘backend.sales-graphs’) ->with(‘analytics’, $analytics); }); Just as with view composers, Blade’s service injection offers a convenient shortcut to reduce duplication in your route definitions. Normally, the content of a view using our analytics service might look like Example 4-27. Example 4-27. Using an injected navigation service in a view@newlinesToBr($message->body)
The $expression parameter received by the closure represents whatever’s within the parentheses. As you can see, we then generate a valid PHP code snippet and return it. $expression Parameter Scoping Before Laravel 5.3 Before Laravel 5.3, the $expression parameter also included the parentheses themselves. So, in Example 4-30, $expression (which is $message->body in Laravel 5.3 and later) would have instead been ($message->body), and we would’ve had to write . Custom Blade Directives | 81 If you find yourself constantly writing the same conditional logic over and over, you should consider a Blade directive. Example: Using Custom Blade Directives for a Multitenant App Let’s imagine we’re building an application that supports multitenancy, which means users might be visiting the site from www.myapp.com, client1.myapp.com, cli‐ ent2.myapp.com, or elsewhere. Suppose we have written a class to encapsulate some of our multitenancy logic and named it Context. This class will capture information and logic about the context of the current visit, such as who the authenticated user is and whether the user is visiting the public website or a client subdomain. We’ll probably frequently resolve that Context class in our views and perform condi‐ tionals on it, like in Example 4-31. app(‘context’) is a shortcut to get an instance of a class from the container, which we’ll learn more about in Chapter 11. Example 4-31. Conditionals on context without a custom Blade directive @if (app(‘context’)->isPublic()) © Copyright MyApp LLC @else © Copyright {{ app(‘context’)->client->name }} @endif What if we could simplify @if (app(‘context’)->isPublic()) to just @ifPublic? Let’s do it. Check out Example 4-32. Example 4-32. Conditionals on context with a custom Blade directive // Binding Blade::directive(‘ifPublic’, function () { return “isPublic()): ?>”; }); // In use @ifPublic © Copyright MyApp LLC @else © Copyright {{ app(‘context’)->client->name }} @endif Since this resolves to a simple if statement, we can still rely on the native @else and @endif conditionals. But if we wanted, we could also create a custom @elseIfClient directive, or a separate @ifClient directive, or really whatever else we want. 82 | Chapter 4: Blade Templating Easier Custom Directives for “if” Statements While custom Blade directives are powerful, the most common use for them is if statements. So there’s a simpler way to create custom “if ” directives: Blade::if(). Example 4-33 shows how we could refactor Example 4-32 using the Blade::if() method: Example 4-33. Dening a custom “if” Blade directive // Binding Blade::if(‘ifPublic’, function () { return (app(‘context’))->isPublic(); }); You’ll use the directives exactly the same way, but as you can see, defining them is a bit simpler. Instead of having to manually type out PHP braces, you can just write a closure that returns a Boolean. Testing The most common method of testing views is through application testing, meaning that you’re actually calling the route that displays the views and ensuring the views have certain content (see Example 4-34). You can also click buttons or submit forms and ensure that you are redirected to a certain page, or that you see a certain error. (You’ll learn more about testing in Chapter 12.) Example 4-34. Testing that a view displays certain content // EventsTest.php public function test_list_page_shows_all_events() { $event1 = factory(Event::class)->create(); $event2 = factory(Event::class)->create(); $this->get(‘events’) ->assertSee($event1->title) ->assertSee($event2->title); } You can also test that a certain view has been passed a particular set of data, which, if it accomplishes your testing goals, is less fragile than checking for certain text on the page. Example 4-35 demonstrates this approach. Testing | 83 Example 4-35. Testing that a view was passed certain content // EventsTest.php public function test_list_page_shows_all_events() { $event1 = factory(Event::class)->create(); $event2 = factory(Event::class)->create(); $response = $this->get(‘events’); $response->assertViewHas(‘events’, Event::all()); $response->assertViewHasAll([ ‘events’ => Event::all(), ‘title’ => ‘Events Page’, ]); $response->assertViewMissing(‘dogs’); } Different Names for Testing Methods Prior to Laravel 5.4 In projects running versions of Laravel prior to 5.4, get() and assertSee() should be replaced by visit() and see(). In 5.3 we gained the ability to pass a closure to assertViewHas(), meaning we can customize how we want to check more complex data structures. Example 4-36 illus‐ trates how we might use this. Example 4-36. Passing a closure to assertViewHas() // EventsTest.php public function test_list_page_shows_all_events() { $event1 = factory(Event::class)->create(); $response = $this->get(“events/{ $event->id }”); $response->assertViewHas(‘event’, function ($event) use ($event1) { return $event->id === $event1->id; }); } TL;DR Blade is Laravel’s templating engine. Its primary focus is a clear, concise, and expres‐ sive syntax with powerful inheritance and extensibility. Its “safe echo” brackets are {{ and }}, its unprotected echo brackets are {!! and !!}, and it has a series of custom tags called directives that all begin with @ (@if and @unless, for example). 84 | Chapter 4: Blade Templating You can define a parent template and leave “holes” in it for content using @yield and @section/@show. You can then teach its child views to extend it using @extends(‘parent.view’), and define their sections using @section/@endsection. You use @parent to reference the content of the block’s parent. View composers make it easy to define that, every time a particular view or subview loads, it should have certain information available to it. And service injection allows the view itself to request data straight from the application container. TL;DR | 85 CHAPTER 5 Databases and Eloquent Laravel provides a suite of tools for interacting with your application’s databases, but the most notable is Eloquent, Laravel’s ActiveRecord ORM (object-relational mapper). Eloquent is one of Laravel’s most popular and influential features. It’s a great example of how Laravel is different from the majority of PHP frameworks; in a world of Data‐ Mapper ORMs that are powerful but complex, Eloquent stands out for its simplicity. There’s one class per table, which is responsible for retrieving, representing, and per‐ sisting data in that table. Whether or not you choose to use Eloquent, however, you’ll still get a ton of benefit from the other database tools Laravel provides. So, before we dig into Eloquent, we’ll start by covering the basics of Laravel’s database functionality: migrations, seeders, and the query builder. Then we’ll cover Eloquent: defining your models; inserting, updating, and deleting; customizing your responses with accessors, mutators, and attribute casting; and finally relationships. There’s a lot going on here, and it’s easy to get overwhelmed, but if we take it one step at a time we’ll make it through. Conguration Before we get into how to use Laravel’s database tools, let’s pause for a second and go over how to configure your database credentials and connections. The configuration for database access lives in cong/database.php and .env. Like many other configuration areas in Laravel, you can define multiple “connections” and then decide which the code will use by default. 87 Database Connections By default, there’s one connection for each of the drivers, as you can see in Example 5-1. Example 5-1. e default database connections list ‘connections’ => [ ‘sqlite’ => [ ‘driver’ => ‘sqlite’, ‘database’ => env(‘DB_DATABASE’, database_path(‘database.sqlite’)), ‘prefix’ => ”, ], ‘mysql’ => [ ‘driver’ => ‘mysql’, ‘host’ => env(‘DB_HOST’, ‘127.0.0.1’), ‘port’ => env(‘DB_PORT’, ‘3306’), ‘database’ => env(‘DB_DATABASE’, ‘forge’), ‘username’ => env(‘DB_USERNAME’, ‘forge’), ‘password’ => env(‘DB_PASSWORD’, ”), ‘unix_socket’ => env(‘DB_SOCKET’, ”), ‘charset’ => ‘utf8’, ‘collation’ => ‘utf8_unicode_ci’, ‘prefix’ => ”, ‘strict’ => false, ‘engine’ => null, ], ‘pgsql’ => [ ‘driver’ => ‘pgsql’, ‘host’ => env(‘DB_HOST’, ‘127.0.0.1’), ‘port’ => env(‘DB_PORT’, ‘5432’), ‘database’ => env(‘DB_DATABASE’, ‘forge’), ‘username’ => env(‘DB_USERNAME’, ‘forge’), ‘password’ => env(‘DB_PASSWORD’, ”), ‘charset’ => ‘utf8’, ‘prefix’ => ”, ‘schema’ => ‘public’, ‘sslmode’ => ‘prefer’, ], ‘sqlsrv’ => [ ‘driver’ => ‘sqlsrv’, ‘host’ => env(‘DB_HOST’, ‘localhost’), ‘port’ => env(‘DB_PORT’, ‘1433’), ‘database’ => env(‘DB_DATABASE’, ‘forge’), ‘username’ => env(‘DB_USERNAME’, ‘forge’), ‘password’ => env(‘DB_PASSWORD’, ”), ‘charset’ => ‘utf8’, 88 | Chapter 5: Databases and Eloquent ‘prefix’ => ”, ], ] Nothing is stopping you from deleting or modifying these named connections or cre‐ ating your own. You can create new named connections, and you’ll be able to set the drivers (MySQL, Postgres, etc.) in them. So, while there’s one connection per driver by default, that’s not a constraint; you could have five different connections, all with the mysql driver, if you wanted. Each connection allows you to define the properties necessary for connecting to and customizing each connection type. There are a few reasons for the idea of multiple drivers. To start with, the “connec‐ tions” section as it comes out of the box is a simple template that makes it easy to start apps that use any of the supported database connection types. In many apps, you can pick the database connection you’ll be using, fill out its information, and even delete the others if you’d like. I usually just keep them all there, in case I might eventually use them. But there are also some cases where you might need multiple connections within the same application. For example, you might use different database connections for two different types of data, or you might read from one and write to another. Support for multiple connections makes this possible. Other Database Conguration Options The cong/database.php configuration section has quite a few other configuration settings. You can configure Redis access, customize the table name used for migra‐ tions, determine the default connection, and toggle whether non-Eloquent calls return stdClass or array instances. With any service in Laravel that allows connections from multiple sources—sessions can be backed by the database or file storage, the cache can use Redis or Memcached, databases can use MySQL or PostgreSQL—you can define multiple connections and also choose that a particular connection will be the “default,” meaning it will be used any time you don’t explicitly ask for a particular connection. Here’s how you ask for a specific connection, if you want to: $users = DB::connection(‘secondary’)->select(‘select * from users’); [role=”less_space pagebreak-before”]’ === Migrations Modern frameworks like Laravel make it easy to define your database structure with code-driven migrations. Every new table, column, index, and key can be defined in Conguration | 89 code, and any new environment can be brought from bare database to your app’s per‐ fect schema in seconds. Dening Migrations A migration is a single file that defines two things: the modifications desired when running this migration up and, optionally, the modifications desired when running this migration down. “Up” and “Down” in Migrations Migrations are always run in order by date. Every migration file is named something like this: 2018_10_12_000000_create_users_table.php. When a new system is migra‐ ted, the system grabs each migration, starting at the earliest date, and runs its up() method—you’re migrating it “up” at this point. But the migration system also allows you to “roll back” your most recent set of migrations. It’ll grab each of them and run its down() method, which should undo whatever changes the up migration made. So, the up() method of a migration should “do” its migration, and the down() method should “undo” it. Example 5-2 shows what the default “create users table” migration that comes with Laravel looks like. Example 5-2. Laravel’s default “create users table” migration bigIncrements(‘id’); $table->string(‘name’); $table->string(’email’)->unique(); $table->timestamp(’email_verified_at’)->nullable(); $table->string(‘password’); $table->rememberToken(); 90 | Chapter 5: Databases and Eloquent $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists(‘users’); } } Email Verication The email_verified_at column is only present in apps built in Laravel 5.7 and later. It stores a timestamp indicating when the user verified their email address. As you can see, we have an up() method and a down() method. up() tells the migra‐ tion to create a new table named users with a few fields, and down() tells it to drop the users table. Creating a migration As you will see in Chapter 8, Laravel provides a series of command-line tools you can use to interact with your app and generate boilerplate files. One of these commands allows you to create a migration file. You can run it using php artisan make:migra tion, and it has a single parameter, which is the name of the migration. For example, to create the table we just covered, you would run php artisan make:migration create_users_table. There are two flags you can optionally pass to this command. –create=table_name prefills the migration with code designed to create a table named table_name, and –table=table_name just prefills the migration for modifications to an existing table. Here are a few examples: php artisan make:migration create_users_table php artisan make:migration add_votes_to_users_table –table=users php artisan make:migration create_users_table –create=users Creating tables We already saw in the default create_users_table migration that our migrations depend on the Schema facade and its methods. Everything we can do in these migra‐ tions will rely on the methods of Schema. Conguration | 91 To create a new table in a migration, use the create() method—the first parameter is the table name, and the second is a closure that defines its columns: Schema::create(‘users’, function (Blueprint $table) { // Create columns here }); Creating columns To create new columns in a table, whether in a create table call or a modify table call, use the instance of Blueprint that’s passed into your closure: Schema::create(‘users’, function (Blueprint $table) { $table->string(‘name’); }); Let’s look at the various methods available on Blueprint instances for creating col‐ umns. I’ll describe how they work in MySQL, but if you’re using another database, Laravel will just use the closest equivalent. The following are the simple field Blueprint methods: integer(colName), tinyInteger(colName), smallInteger(colName), mediumInteger(colName), bigInteger(colName) Adds an INTEGER type column, or one of its many variations string(colName, length) Adds a VARCHAR type column with an optional length binary(colName) Adds a BLOB type column boolean(colName) Adds a BOOLEAN type column (a TINYINT(1) in MySQL) char(colName, length) Adds a CHAR column with an optional length datetime(colName) Adds a DATETIME column decimal(colName, precision, scale) Adds a DECIMAL column, with precision and scale—for example, deci mal(‘amount’, 5, 2) specifies a precision of 5 and a scale of 2 double(colName, total digits, digits after decimal) Adds a DOUBLE column—for example, double(‘tolerance’, 12, 8) specifies 12 digits long, with 8 of those digits to the right of the decimal place, as in 7204.05691739 92 | Chapter 5: Databases and Eloquent enum(colName, [choiceOne, choiceTwo]) Adds an ENUM column, with provided choices float(colName, precision, scale) Adds a FLOAT column (same as double in MySQL) json(colName) and jsonb(colName) Adds a JSON or JSONB column (or a TEXT column in Laravel 5.1) text(colName), mediumText(colName), longText(colName) Adds a TEXT column (or its various sizes) time(colName) Adds a TIME column timestamp(colName) Adds a TIMESTAMP column uuid(colName) Adds a UUID column (CHAR(36) in MySQL) And these are the special (joined) Blueprint methods: increments(colName) and bigIncrements(colName) Add an unsigned incrementing INTEGER or BIG INTEGER primary key ID timestamps() and nullableTimestamps() Adds created_at and updated_at timestamp columns rememberToken() Adds a remember_token column (VARCHAR(100)) for user “remember me” tokens softDeletes() Adds a deleted_at timestamp for use with soft deletes morphs(colName) For a provided colName, adds an integer colName_id and a string colName_type (e.g., morphs(tag) adds integer tag_id and string tag_type); for use in polymor‐ phic relationships Building extra properties fluently Most of the properties of a field definition—its length, for example—are set as the second parameter of the field creation method, as we saw in the previous section. But there are a few other properties that we’ll set by chaining more method calls after the creation of the column. For example, this email field is nullable and will be placed (in MySQL) right after the last_name field: Conguration | 93 Schema::table(‘users’, function (Blueprint $table) { $table->string(’email’)->nullable()->after(‘last_name’); }); The following methods are used to set additional properties of a field: nullable() Allows NULL values to be inserted into this column default(‘default content’) Specifies the default content for this column if no value is provided unsigned() Marks integer columns as unsigned (not negative or positive, but just an integer) first() (MySQL only) Places the column first in the column order after(colName) (MySQL only) Places the column after another column in the column order unique() Adds a UNIQUE index primary() Adds a primary key index index() Adds a basic index Note that unique(), primary(), and index() can also be used outside of the fluent column building context, which we’ll cover later. Dropping tables If you want to drop a table, there’s a dropIfExists() method on Schema that takes one parameter, the table name: Schema::dropIfExists(‘contacts’); Modifying columns To modify a column, just write the code you would write to create the column as if it were new, and then append a call to the change() method after it. Required Dependency Before Modifying Columns Before you modify any columns (or drop any columns in SQLite), you’ll need to run composer require doctrine/dbal. 94 | Chapter 5: Databases and Eloquent So, if we have a string column named name that has a length of 255 and we want to change its length to 100, this is how we would write it: Schema::table(‘users’, function (Blueprint $table) { $table->string(‘name’, 100)->change(); }); The same is true if we want to adjust any of its properties that aren’t defined in the method name. To make a field nullable, we do this: Schema::table(‘contacts’, function (Blueprint $table) { $table->string(‘deleted_at’)->nullable()->change(); }); Here’s how we rename a column: Schema::table(‘contacts’, function (Blueprint $table) { $table->renameColumn(‘promoted’, ‘is_promoted’); }); And this is how we drop a column: Schema::table(‘contacts’, function (Blueprint $table) { $table->dropColumn(‘votes’); }); Modifying Multiple Columns at Once in SQLite If you try to drop or modify multiple columns within a single migration closure and you are using SQLite, you’ll run into errors. In Chapter 12 I recommend that you use SQLite for your testing database, so even if you’re using a more traditional database, you may want to consider this a limitation for testing purposes. However, you don’t have to create a new migration for each. Instead, just create multiple calls to Schema::table() within the up() method of your migration: public function up() { Schema::table(‘contacts’, function (Blueprint $table) { $table->dropColumn(‘is_promoted’); }); Schema::table(‘contacts’, function (Blueprint $table) { $table->dropColumn(‘alternate_email’); }); } Conguration | 95 Indexes and foreign keys We’ve covered how to create, modify, and delete columns. Let’s move on to indexing and relating them. If you’re not familiar with indexes, your databases can survive if you just never use them, but they’re pretty important for performance optimization and for some data integrity controls with regard to related tables. I’d recommend reading up on them, but if you absolutely must, you can skip this section for now. Adding indexes. Check out Example 5-3 for examples of how to add indexes to your column. Example 5-3. Adding column indexes in migrations // After columns are created… $table->primary(‘primary_id’); // Primary key; unnecessary if used increments() $table->primary([‘first_name’, ‘last_name’]); // Composite keys $table->unique(’email’); // Unique index $table->unique(’email’, ‘optional_custom_index_name’); // Unique index $table->index(‘amount’); // Basic index $table->index(‘amount’, ‘optional_custom_index_name’); // Basic index Note that the first example, primary(), is not necessary if you’re using the increments() or bigIncrements() methods to create your index; this will automati‐ cally add a primary key index for you. Removing indexes. We can remove indexes as shown in Example 5-4. Example 5-4. Removing column indexes in migrations $table->dropPrimary(‘contacts_id_primary’); $table->dropUnique(‘contacts_email_unique’); $table->dropIndex(‘optional_custom_index_name’); // If you pass an array of column names to dropIndex, it will // guess the index names for you based on the generation rules $table->dropIndex([’email’, ‘amount’]); Adding and removing foreign keys. To add a foreign key that defines that a particular column references a column on another table, Laravel’s syntax is simple and clear: $table->foreign(‘user_id’)->references(‘id’)->on(‘users’); Here we’re adding a foreign index on the user_id column, showing that it references the id column on the users table. Couldn’t get much simpler. 96 | Chapter 5: Databases and Eloquent If we want to specify foreign key constraints, we can do that too, with onDelete() and onUpdate(). For example: $table->foreign(‘user_id’) ->references(‘id’) ->on(‘users’) ->onDelete(‘cascade’); To drop a foreign key, we can either delete it by referencing its index name (which is automatically generated by combining the names of the columns and tables being ref‐ erenced): $table->dropForeign(‘contacts_user_id_foreign’); or by passing it an array of the fields that it’s referencing in the local table: $table->dropForeign([‘user_id’]); Running Migrations Once you have your migrations defined, how do you run them? There’s an Artisan command for that: php artisan migrate This command runs all “outstanding” migrations (by running the up() method on each). Laravel keeps track of which migrations you have run and which you haven’t. Every time you run this command, it checks whether you’ve run all available migra‐ tions, and if you haven’t, it’ll run any that remain. There are a few options in this namespace that you can work with. First, you can run your migrations and your seeds (which we’ll cover next): php artisan migrate –seed You can also run any of the following commands: migrate:install Creates the database table that keeps track of which migrations you have and haven’t run; this is run automatically when you run your migrations, so you can basically ignore it. migrate:reset Rolls back every database migration you’ve run on this instance. migrate:refresh Rolls back every database migration you’ve run on this instance, and then runs every migration available. It’s the same as running migrate:reset and then migrate, one after the other. Conguration | 97 migrate:fresh Drops all of your tables and runs every migration again. It’s the same as refresh but doesn’t bother with the “down” migrations—it just deletes the tables and then runs the “up” migrations again. migrate:rollback Rolls back just the migrations that ran the last time you ran migrate, or, with the added option –step=n, rolls back the number of migrations you specify. migrate:status Shows a table listing every migration, with a Y or N next to each showing whether or not it has run yet in this environment. Migrating with Homestead/Vagrant If you’re running migrations on your local machine and your .env file points to a database in a Vagrant box, your migrations will fail. You’ll need to ssh into your Vagrant box and then run the migra‐ tions from there. The same is true for seeds and any other Artisan commands that affect or read from the database. Seeding Seeding with Laravel is so simple, it has gained widespread adoption as a part of nor‐ mal development workflows in a way it hasn’t in previous PHP frameworks. There’s a database/seeds folder that comes with a DatabaseSeeder class, which has a run() method that is called when you call the seeder. There are two primary ways to run the seeders: along with a migration, or separately. To run a seeder along with a migration, just add –seed to any migration call: php artisan migrate –seed php artisan migrate:refresh –seed And to run it independently: php artisan db:seed php artisan db:seed –class=VotesTableSeeder This will call the run() method of the DatabaseSeeder by default, or the seeder class specified by –class. Creating a Seeder To create a seeder, use the make:seeder Artisan command: php artisan make:seeder ContactsTableSeeder 98 | Chapter 5: Databases and Eloquent You’ll now see a ContactsTableSeeder class show up in the database/seeds directory. Before we edit it, let’s add it to the DatabaseSeeder class, as shown in Example 5-5, so it will run when we run our seeders. Example 5-5. Calling a custom seeder from DatabaseSeeder.php // database/seeds/DatabaseSeeder.php … public function run() { $this->call(ContactsTableSeeder::class); } Now let’s edit the seeder itself. The simplest thing we can do there is manually insert a record using the DB facade, as illustrated in Example 5-6. Example 5-6. Inserting database records in a custom seeder insert([ ‘name’ => ‘Lupita Smith’, ’email’ => ‘lupita@gmail.com’, ]); } } This will get us a single record, which is a good start. But for truly functional seeds, you’ll likely want to loop over some sort of random generator and run this insert() many times, right? Laravel has a feature for that. Model Factories Model factories define one (or more) patterns for creating fake entries for your data‐ base tables. By default each factory is named after an Eloquent class, but you can also just name them after the table if you’re not going to work with Eloquent. Example 5-7 shows the same factory set up both ways. Seeding | 99 Example 5-7. Dening model factories with Eloquent class and table name keys $factory->define(User::class, function (Faker\Generator $faker) { return [ ‘name’ => $faker->name, ]; }); $factory->define(‘users’, function (Faker\Generator $faker) { return [ ‘name’ => $faker->name, ]; }); Theoretically you can name these factories anything you like, but naming the factory after your Eloquent class is the most idiomatic approach. Creating a model factory Model factories are located in database/factories. In Laravel 5.5 and later each factory is usually defined in its own class, with a key (name) and a closure defining how to create a new instance of the defined class. The $factory->define() method takes the factory name as the first parameter and a closure that’s run for each generation as the second parameter. The Model Factory File in Laravel 5.4 and Earlier In Laravel prior to 5.5, all factories should be defined in database/ factories/ModelFactory.php. There are no separate classes for each factory until 5.5. To generate a new factory class, use the Artisan make:factory command; just like with naming the factory keys, it’s also most common to name factory classes after the Eloquent models they’re meant to generate instances of: php artisan make:factory ContactFactory This will generate a new file within the database/factories directory called ContactFac‐ tory.php. The simplest factory we could define for a contact might look something like Example 5-8: Example 5-8. e simplest possible factory denition $factory->define(Contact::class, function (Faker\Generator $faker) { return [ ‘name’ => ‘Lupita Smith’, ’email’ => ‘lupita@gmail.com’, 100 | Chapter 5: Databases and Eloquent ]; }); Now we can use the factory() global helper to create an instance of Contact in our seeding and testing: // Create one $contact = factory(Contact::class)->create(); // Create many factory(Contact::class, 20)->create(); However, if we used that factory to create 20 contacts, all 20 would have the same information. That’s less useful. We will get even more benefit from model factories when we take advantage of the instance of Faker that’s passed into the closure; Faker makes it easy to randomize the creation of structured fake data. The previous example now turns into Example 5-9. Example 5-9. A simple factory, modied to use Faker $factory->define(Contact::class, function (Faker\Generator $faker) { return [ ‘name’ => $faker->name, ’email’ => $faker->email, ]; }); Now, every time we create a fake contact using this model factory, all of our proper‐ ties will be randomly generated. Guaranteeing the Uniqueness of Randomly Generated Data If you want to guarantee that the randomly generated values of any given entry are unique compared to the other randomly generated values during that PHP process, you can use Faker’s unique() method: return [’email’ => $faker->unique()->email]; Using a model factory There are two primary contexts in which we’ll use model factories: testing, which we’ll cover in Chapter 12, and seeding, which we’ll cover here. Let’s write a seeder using a model factory; take a look at Example 5-10. Seeding | 101 Example 5-10. Using model factories factory(Post::class)->create([ ‘title’ => ‘My greatest post ever’, ]); // Pro-level factory; but don’t get overwhelmed! factory(User::class, 20)->create()->each(function ($u) use ($post) { $post->comments()->save(factory(Comment::class)->make([ ‘user_id’ => $u->id, ])); }); To create an object, we use the factory() global helper and pass it the name of the factory—which, as we just saw, is the name of the Eloquent class we’re generating an instance of. That returns the factory, and then we can run one of two methods on it: make() or create(). Both methods generate an instance of this specified model, using the definition in the factory file. The difference is that make() creates the instance but doesn’t (yet) save it to the database, whereas create() saves it to the database instantly. You can see both in use in the two examples in Example 5-10. The second example will make more sense once we cover relationships in Eloquent later in this chapter. Overriding properties when calling a model factory. If you pass an array to either make() or create(), you can override specific keys from the factory, like we did in Example 5-10 to set the user_id on the comment and to manually set the title of our post. Generating more than one instance with a model factory. If you pass a number as the sec‐ ond parameter to the factory() helper, you can specify that you’re creating more than one instance. Instead of returning a single instance, it’ll return a collection of instances. This means you can treat the result like an array, you can associate each of its instances with another entity, or you can use other entity methods on each instance—like we used each() in Example 5-10 to add a comment from each newly created user. Pro-level model factories Now that we’ve covered the most common uses for and arrangements of model facto‐ ries, let’s dive into some of the more complicated ways we can use them. 102 | Chapter 5: Databases and Eloquent Attaching relationships when dening model factories. Sometimes you need to create a related item along with the item you’re creating. You can use a closure on that prop‐ erty to create a related item and pull its ID, as shown in Example 5-11. Example 5-11. Creating a related term item in a seeder $factory->define(Contact::class, function (Faker\Generator $faker) { return [ ‘name’ => ‘Lupita Smith’, ’email’ => ‘lupita@gmail.com’, ‘company_id’ => function () { return factory(App\Company::class)->create()->id; }, ]; }); Each closure is passed a single parameter, which contains the array form of the gener‐ ated item up until that point. This can be used in other ways, as demonstrated in Example 5-12. Example 5-12. Using values from other parameters in a seeder $factory->define(Contact::class, function (Faker\Generator $faker) { return [ ‘name’ => ‘Lupita Smith’, ’email’ => ‘lupita@gmail.com’, ‘company_id’ => function () { return factory(App\Company::class)->create()->id; }, ‘company_size’ => function ($contact) { // Uses the “company_id” property generated above return App\Company::find($contact[‘company_id’])->size; }, ]; }); Dening and accessing multiple model factory states. Let’s go back to ContactFactory.php (from Example 5-8 and Example 5-9) for a second. We have a base Contact factory defined: $factory->define(Contact::class, function (Faker\Generator $faker) { return [ ‘name’ => $faker->name, ’email’ => $faker->email, ]; }); Seeding | 103 But sometimes you need more than one factory for a class of object. What if we need to be able to add some contacts who are very important people (VIPs)? We can use the state() method to define a second factory state for this, as seen in Example 5-13. The first parameter to state() is still the name of the entity you’re generating, the second is the name of your state, and the third is an array of any attributes you want to specifically set for this state. Example 5-13. Dening multiple factory states for the same model $factory->define(Contact::class, function (Faker\Generator $faker) { return [ ‘name’ => $faker->name, ’email’ => $faker->email, ]; }); $factory->state(Contact::class, ‘vip’, [ ‘vip’ => true, ]); If the modified attributes require more than a simple static value, you can pass a clo‐ sure instead of an array as the second parameter and then return an array of the attributes you want to modify, like in Example 5-14. Example 5-14. Specifying a factory state with a closure $factory->state(Contact::class, ‘vip’, function (Faker\Generator $faker) { return [ ‘vip’ => true, ‘company’ => $faker->company, ]; }); Now, let’s make an instance of a specific state: $vip = factory(Contact::class, ‘vip’)->create(); $vips = factory(Contact::class, ‘vip’, 3)->create(); Factory States Prior to Laravel 5.3 In projects running versions of Laravel prior to 5.3, factory states were called factory types, and you’ll want to use $factory- >defineAs() instead of $factory->state(). You can learn more about this in the 5.2 docs. 104 | Chapter 5: Databases and Eloquent Whew. That was a lot. Don’t worry if that was tough to follow—the last bit was defi‐ nitely higher-level stuff. Let’s get back down to the basics and talk about the core of Laravel’s database tooling: the query builder. Query Builder Now that you’re connected and you’ve migrated and seeded your tables, let’s get started with how to use the database tools. At the core of every piece of Laravel’s database functionality is the query builder, a fluent interface for interacting with sev‐ eral different types of databases with a single clear API. What Is a Fluent Interface? A fluent interface is one that primarily uses method chaining to provide a simpler API to the end user. Rather than expecting all of the relevant data to be passed into either a constructor or a method call, fluent call chains can be built gradually, with consecutive calls. Consider this comparison: // Non-fluent: $users = DB::select([‘table’ => ‘users’, ‘where’ => [‘type’ => ‘donor’]]); // Fluent: $users = DB::table(‘users’)->where(‘type’, ‘donor’)->get(); Laravel’s database architecture can connect to MySQL, Postgres, SQLite, and SQL Server through a single interface, with just the change of a few configuration settings. If you’ve ever used a PHP framework, you’ve likely used a tool that allows you to run “raw” SQL queries with basic escaping for security. The query builder is that, with a lot of convenience layers and helpers on top. So, let’s start with some simple calls. Basic Usage of the DB Facade Before we get into building complex queries with fluent method chaining, let’s take a look at a few sample query builder commands. The DB facade is used both for query builder chaining and for simpler raw queries, as illustrated in Example 5-15. Example 5-15. Sample raw SQL and query builder usage // Basic statement DB::statement(‘drop table users’); // Raw select, and parameter binding DB::select(‘select * from contacts where validated = ?’, [true]); Query Builder | 105 // Select using the fluent builder $users = DB::table(‘users’)->get(); // Joins and other complex calls DB::table(‘users’) ->join(‘contacts’, function ($join) { $join->on(‘users.id’, ‘=’, ‘contacts.user_id’) ->where(‘contacts.type’, ‘donor’); }) ->get(); Raw SQL As you saw in Example 5-15, it’s possible to make any raw call to the database using the DB facade and the statement() method: DB::statement(‘SQL statement here’). But there are also specific methods for various common actions: select(), insert(), update(), and delete(). These are still raw calls, but there are differences. First, using update() and delete() will return the number of rows affected, whereas statement() won’t; second, with these methods it’s clearer to future developers exactly what sort of statement you’re making. Raw selects The simplest of the specific DB methods is select(). You can run it without any addi‐ tional parameters: $users = DB::select(‘select * from users’); This will return a collection of stdClass objects. Illuminate Collections Prior to Laravel 5.3, the DB facade returned a stdClass object for methods that return only one row (like first()) and an array for any that return multiple rows (like all()). In Laravel 5.3+, the DB facade, like Eloquent, returns a collection for any method that returns (or can return) multiple rows. The DB facade returns an instance of Illuminate\Support\Collection and Eloquent returns an instance of Illuminate \Database\Eloquent\Collection, which extends Illuminate\Support\Collection with a few Eloquent-specific methods. Collection is like a PHP array with superpowers, allowing you to run map(), filter(), reduce(), each(), and much more on your data. You can learn more about collections in Chapter 17. 106 | Chapter 5: Databases and Eloquent Parameter bindings and named bindings Laravel’s database architecture allows for the use of PDO parameter binding, which protects your queries from potential SQL attacks. Passing a parameter to a statement is as simple as replacing the value in your statement with a ?, then adding the value to the second parameter of your call: $usersOfType = DB::select( ‘select * from users where type = ?’, [$type] ); You can also name those parameters for clarity: $usersOfType = DB::select( ‘select * from users where type = :type’, [‘type’ => $userType] ); Raw inserts From here, the raw commands all look pretty much the same. Raw inserts look like this: DB::insert( ‘insert into contacts (name, email) values (?, ?)’, [‘sally’, ‘sally@me.com’] ); Raw updates Updates look like this: $countUpdated = DB::update( ‘update contacts set status = ? where id = ?’, [‘donor’, $id] ); Raw deletes And deletes look like this: $countDeleted = DB::delete( ‘delete from contacts where archived = ?’, [true] ); Chaining with the Query Builder Up until now, we haven’t actually used the query builder, per se. We’ve just used sim‐ ple method calls on the DB facade. Let’s actually build some queries. Query Builder | 107 The query builder makes it possible to chain methods together to, you guessed it, build a query. At the end of your chain you’ll use some method—likely get()—to trigger the actual execution of the query you’ve just built. Let’s take a look at a quick example: $usersOfType = DB::table(‘users’) ->where(‘type’, $type) ->get(); Here, we built our query—users table, $type type—and then we executed the query and got our result. Let’s take a look at what methods the query builder allows you to chain. The methods can be split up into what I’ll call constraining methods, modifying methods, condi‐ tional methods, and ending/returning methods. Constraining methods These methods take the query as it is and constrain it to return a smaller subset of possible data: select() Allows you to choose which columns you’re selecting: $emails = DB::table(‘contacts’) ->select(’email’, ’email2 as second_email’) ->get(); // Or $emails = DB::table(‘contacts’) ->select(’email’) ->addSelect(’email2 as second_email’) ->get(); where() Allows you to limit the scope of what’s being returned using WHERE. By default, the signature of the where() method is that it takes three parameters—the col‐ umn, the comparison operator, and the value: $newContacts = DB::table(‘contact’) ->where(‘created_at’, ‘>’, now()->subDay()) ->get(); However, if your comparison is =, which is the most common comparison, you can drop the second operator: $vipContacts = DB::table(‘contacts’)->where(‘vip’,true)->get(); If you want to combine where() statements, you can either chain them after each other, or pass an array of arrays: 108 | Chapter 5: Databases and Eloquent $newVips = DB::table(‘contacts’) ->where(‘vip’, true) ->where(‘created_at’, ‘>’, now()->subDay()); // Or $newVips = DB::table(‘contacts’)->where([ [‘vip’, true], [‘created_at’, ‘>’, now()->subDay()], ]); orWhere() Creates simple OR WHERE statements: $priorityContacts = DB::table(‘contacts’) ->where(‘vip’, true) ->orWhere(‘created_at’, ‘>’, now()->subDay()) ->get(); To create a more complex OR WHERE statement with multiple conditions, pass orWhere() a closure: $contacts = DB::table(‘contacts’) ->where(‘vip’, true) ->orWhere(function ($query) { $query->where(‘created_at’, ‘>’, now()->subDay()) ->where(‘trial’, false); }) ->get(); Query Builder | 109 Potential Confusion with Multiple where() and orWhere() Calls If you are using orWhere() calls in conjunction with multiple where() calls, you need to be very careful to ensure the query is doing what you think it is. This isn’t because of any fault with Lara‐ vel, but because a query like the following might not do what you expect: $canEdit = DB::table(‘users’) ->where(‘admin’, true) ->orWhere(‘plan’, ‘premium’) ->where(‘is_plan_owner’, true) ->get(); SELECT * FROM users WHERE admin = 1 OR plan = ‘premium’ AND is_plan_owner = 1; If you want to write SQL that says “if this OR (this and this),” which is clearly the intention in the previous example, you’ll want to pass a closure into the orWhere() call: $canEdit = DB::table(‘users’) ->where(‘admin’, true) ->orWhere(function ($query) { $query->where(‘plan’, ‘premium’) ->where(‘is_plan_owner’, true); }) ->get(); SELECT * FROM users WHERE admin = 1 OR (plan = ‘premium’ AND is_plan_owner = 1); whereBetween(colName, [low, high]) Allows you to scope a query to return only rows where a column is between two values (inclusive of the two values): $mediumDrinks = DB::table(‘drinks’) ->whereBetween(‘size’, [6, 12]) ->get(); The same works for whereNotBetween(), but it will select the inverse. whereIn(colName, [1, 2, 3]) Allows you to scope a query to return only rows where a column value is in an explicitly provided list of options: $closeBy = DB::table(‘contacts’) ->whereIn(‘state’, [‘FL’, ‘GA’, ‘AL’]) ->get(); The same works for whereNotIn(), but it will select the inverse. 110 | Chapter 5: Databases and Eloquent whereNull(colName) and whereNotNull(colName) Allow you to select only rows where a given column is NULL or is NOT NULL, respectively. whereRaw() Allows you to pass in a raw, unescaped string to be added after the WHERE state‐ ment: $goofs = DB::table(‘contacts’)->whereRaw(‘id = 12345’)->get() Beware of SQL Injection! Any SQL queries passed to whereRaw() will not be escaped. Use this method carefully and infrequently; this is a prime opportunity for SQL injection attacks in your app. whereExists() Allows you to select only rows that, when passed into a provided subquery, return at least one row. Imagine you only want to get those users who have left at least one comment: $commenters = DB::table(‘users’) ->whereExists(function ($query) { $query->select(‘id’) ->from(‘comments’) ->whereRaw(‘comments.user_id = users.id’); }) ->get(); distinct() Selects only rows where the selected data is unique when compared to the other rows in the returned data. Usually this is paired with select(), because if you use a primary key, there will be no duplicated rows: $lastNames = DB::table(‘contacts’)->select(‘city’)->distinct()->get(); Modifying methods These methods change the way the query’s results will be output, rather than just lim‐ iting its results: orderBy(colName, direction) Orders the results. The second parameter may be either asc (the default, ascend‐ ing order) or desc (descending order): $contacts = DB::table(‘contacts’) ->orderBy(‘last_name’, ‘asc’) ->get(); Query Builder | 111 groupBy() and having() or havingRaw() Groups your results by a column. Optionally, having() and havingRaw() allow you to filter your results based on properties of the groups. For example, you could look for only cities with at least 30 people in them: $populousCities = DB::table(‘contacts’) ->groupBy(‘city’) ->havingRaw(‘count(contact_id) > 30’) ->get(); skip() and take() Most often used for pagination, these allow you to define how many rows to return and how many to skip before starting the return—like a page number and a page size in a pagination system: // returns rows 31-40 $page4 = DB::table(‘contacts’)->skip(30)->take(10)->get(); latest(colName) and oldest(colName) Sort by the passed column (or created_at if no column name is passed) in descending (latest()) or ascending (oldest()) order. inRandomOrder() Sorts the result randomly. Conditional methods There are two methods, available in Laravel 5.2 and later, that allow you to condition‐ ally apply their “contents” (a closure you pass to them) based on the Boolean state of a value you pass in: when() Given a truthy first parameter, applies the query modification contained in the closure; given a falsy first parameter, it does nothing. Note that the first parame‐ ter could be a Boolean (e.g., $ignoreDrafts, set to true or false), an optional value ($status, pulled from user input and defaulting to null), or a closure that returns either; what matters is that it evaluates to truthy or falsy. For example: $status = request(‘status’); // Defaults to null if not set $posts = DB::table(‘posts’) ->when($status, function ($query) use ($status) { return $query->where(‘status’, $status); }) ->get(); // Or $posts = DB::table(‘posts’) 112 | Chapter 5: Databases and Eloquent ->when($ignoreDrafts, function ($query) { return $query->where(‘draft’, false); }) ->get(); You can also pass a third parameter, another closure, which will only be applied if the first parameter is falsy. unless() The exact inverse of when(). If the first parameter is falsy, it will run the second closure. Ending/returning methods These methods stop the query chain and trigger the execution of the SQL query. Without one of these at the end of the query chain, your return will always just be an instance of the query builder; chain one of these onto a query builder and you’ll actually get a result: get() Gets all results for the built query: $contacts = DB::table(‘contacts’)->get(); $vipContacts = DB::table(‘contacts’)->where(‘vip’, true)->get(); first() and firstOrFail() Get only the first result—like get(), but with a LIMIT 1 added: $newestContact = DB::table(‘contacts’) ->orderBy(‘created_at’, ‘desc’) ->first(); first() fails silently if there are no results, whereas firstOrFail() will throw an exception. If you pass an array of column names to either method, it will return the data for just those columns instead of all columns. find(id) and findOrFail(id) Like first(), but you pass in an ID value that corresponds to the primary key to look up. find() fails silently if a row with that ID doesn’t exist, while findOrFail() will throw an exception: $contactFive = DB::table(‘contacts’)->find(5); value() Plucks just the value from a single field from the first row. Like first(), but if you only want a single column: Query Builder | 113 $newestContactEmail = DB::table(‘contacts’) ->orderBy(‘created_at’, ‘desc’) ->value(’email’); count() Returns an integer count of all of the matching results: $countVips = DB::table(‘contacts’) ->where(‘vip’, true) ->count(); min() and max() Return the minimum or maximum value of a particular column: $highestCost = DB::table(‘orders’)->max(‘amount’); sum() and avg() Return the sum or average of all of the values in a particular column: $averageCost = DB::table(‘orders’) ->where(‘status’, ‘completed’) ->avg(‘amount’); Writing raw queries inside query builder methods with DB::raw You’ve already seen a few custom methods for raw statements—for example, select() has a selectRaw() counterpart that allows you to pass in a string for the query builder to place after the WHERE statement. You can also, however, pass in the result of a DB::raw() call to almost any method in the query builder to achieve the same result: $contacts = DB::table(‘contacts’) ->select(DB::raw(‘*, (score * 100) AS integer_score’)) ->get(); Joins Joins can sometimes be a pain to define, and there’s only so much a framework can do to make them simpler, but the query builder does its best. Let’s look at a sample: $users = DB::table(‘users’) ->join(‘contacts’, ‘users.id’, ‘=’, ‘contacts.user_id’) ->select(‘users.*’, ‘contacts.name’, ‘contacts.status’) ->get(); The join() method creates an inner join. You can also chain together multiple joins one after another, or use leftJoin() to get a left join. Finally, you can create more complex joins by passing a closure into the join() method: 114 | Chapter 5: Databases and Eloquent DB::table(‘users’) ->join(‘contacts’, function ($join) { $join ->on(‘users.id’, ‘=’, ‘contacts.user_id’) ->orOn(‘users.id’, ‘=’, ‘contacts.proxy_user_id’); }) ->get(); Unions You can union two queries (join their results together into one result set) by creating them first and then using the union() or unionAll() method: $first = DB::table(‘contacts’) ->whereNull(‘first_name’); $contacts = DB::table(‘contacts’) ->whereNull(‘last_name’) ->union($first) ->get(); Inserts The insert() method is pretty simple. Pass it an array to insert a single row or an array of arrays to insert multiple rows, and use insertGetId() instead of insert() to get the autoincrementing primary key ID back as a return: $id = DB::table(‘contacts’)->insertGetId([ ‘name’ => ‘Abe Thomas’, ’email’ => ‘athomas1987@gmail.com’, ]); DB::table(‘contacts’)->insert([ [‘name’ => ‘Tamika Johnson’, ’email’ => ‘tamikaj@gmail.com’], [‘name’ => ‘Jim Patterson’, ’email’ => ‘james.patterson@hotmail.com’], ]); Updates Updates are also simple. Create your update query and, instead of get() or first(), just use update() and pass it an array of parameters: DB::table(‘contacts’) ->where(‘points’, ‘>’, 100) ->update([‘status’ => ‘vip’]); You can also quickly increment and decrement columns using the increment() and decrement() methods. The first parameter of each is the column name, and the sec‐ ond (optional) is the number to increment/decrement by: DB::table(‘contacts’)->increment(‘tokens’, 5); DB::table(‘contacts’)->decrement(‘tokens’); Query Builder | 115 Deletes Deletes are even simpler. Build your query and then end it with delete(): DB::table(‘users’) ->where(‘last_login’, ‘subYear()) ->delete(); You can also truncate the table, which deletes every row and also resets the autoincre‐ menting ID: DB::table(‘contacts’)->truncate(); JSON operations If you have JSON columns, you can update or select rows based on aspects of the JSON structure by using the arrow syntax to traverse children: // Select all records where the “isAdmin” property of the “options” // JSON column is set to true DB::table(‘users’)->where(‘options->isAdmin’, true)->get(); // Update all records, setting the “verified” property // of the “options” JSON column to true DB::table(‘users’)->update([‘options->isVerified’, true]); This is a new feature since Laravel 5.3. Transactions If you’re not familiar with database transactions, they’re a tool that allows you to wrap up a series of database queries to be performed in a batch, which you can choose to roll back, undoing the entire series of queries. Transactions are often used to ensure that all or none, but not some, of a series of related queries are performed—if one fails, the ORM will roll back the entire series of queries. With the Laravel query builder’s transaction feature, if any exceptions are thrown at any point within the transaction closure, all the queries in the transaction will be rol‐ led back. If the transaction closure finishes successfully, all the queries will be com‐ mitted and not rolled back. Let’s take a look at the sample transaction in Example 5-16. Example 5-16. A simple database transaction DB::transaction(function () use ($userId, $numVotes) { // Possibly failing DB query DB::table(‘users’) ->where(‘id’, $userId) ->update([‘votes’ => $numVotes]); 116 | Chapter 5: Databases and Eloquent // Caching query that we don’t want to run if the above query fails DB::table(‘votes’) ->where(‘user_id’, $userId) ->delete(); }); In this example, we can assume we had some previous process that summarized the number of votes from the votes table for a given user. We want to cache that number in the users table and then wipe those votes from the votes table. But, of course, we don’t want to wipe the votes until the update to the users table has run successfully. And we don’t want to keep the updated number of votes in the users table if the votes table deletion fails. If anything goes wrong with either query, the other won’t be applied. That’s the magic of database transactions. Note that you can also manually begin and end transactions—and this applies both for query builder queries and for Eloquent queries. Start with DB::beginTransaction(), end with DB::commit(), and abort with DB::rollBack(): DB::beginTransaction(); // Take database actions if ($badThingsHappened { DB::rollBack(); } // Take other database actions DB::commit(); Introduction to Eloquent Now that we’ve covered the query builder, let’s talk about Eloquent, Laravel’s flagship database tool that’s built on the query builder. Eloquent is an ActiveRecord ORM, which means it’s a database abstraction layer that provides a single interface to interact with multiple database types. “ActiveRecord” means that a single Eloquent class is responsible for not only providing the ability to interact with the table as a whole (e.g., User::all() gets all users), but also represent‐ ing an individual table row (e.g., $sharon = new User). Additionally, each instance is capable of managing its own persistence; you can call $sharon->save() or $sharon->delete(). Eloquent has a primary focus on simplicity, and like the rest of the framework, it relies on “convention over configuration” to allow you to build powerful models with minimal code. Introduction to Eloquent | 117 For example, you can perform all of the operations in Example 5-18 with the model defined in Example 5-17. Example 5-17. e simplest Eloquent model first_name = $request->input(‘first_name’); $contact->last_name = $request->input(‘last_name’); $conatct->email = $request->input(’email’); $contact->save(); return redirect(‘contacts’); } public function show($contactId) { // Return a JSON representation of a contact based on a URL segment; // if the contact doesn’t exist, throw an exception return Contact::findOrFail($contactId); } public function vips() { // Unnecessarily complex example, but still possible with basic Eloquent // class; adds a “formalName” property to every VIP entry return Contact::where(‘vip’, true)->get()->map(function ($contact) { $contact->formalName = “The exalted {$contact->first_name} of the {$contact->last_name}s”; return $contact; }); } How? Convention. Eloquent assumes the table name (Contact becomes contacts), and with that you have a fully functional Eloquent model. Let’s cover how we work with Eloquent models. 118 | Chapter 5: Databases and Eloquent Creating and Dening Eloquent Models First, let’s create a model. There’s an Artisan command for that: php artisan make:model Contact This is what we’ll get, in app/Contact.php: get(); We can see that the Eloquent facade gives us the ability to chain constraints, and from there the constraints get very familiar: $newestContacts = Contact::orderBy(‘created_at’, ‘desc’) ->take(10) ->get(); It turns out that once you move past the initial facade name, you’re just working with Laravel’s query builder. You can do a lot more—we’ll cover that soon—but everything you can do with the query builder on the DB facade you can do on your Eloquent objects. Get one Like we covered earlier in the chapter, you can use first() to return only the first record from a query, or find() to pull just the record with the provided ID. For either, if you append “OrFail” to the method name, it will throw an exception if there are no matching results. This makes findOrFail() a common tool for looking up an entity by a URL segment (or throwing an exception if a matching entity doesn’t exist), like you can see in Example 5-19. 120 | Chapter 5: Databases and Eloquent Example 5-19. Using an Eloquent OrFail() method in a controller method // ContactController public function show($contactId) { return view(‘contacts.show’) ->with(‘contact’, Contact::findOrFail($contactId)); } Any method intended to return a single record (first(), firstOrFail(), find(), or findOrFail()) will return an instance of the Eloquent class. So, Contact::first() will return an instance of the class Contact with the data from the first row in the table filling it out. Exceptions As you can see in Example 5-19, we don’t need to catch Eloquent’s model not found exception (Illuminate\Database\Eloquent \ModelNotFoundException) in our controllers; Laravel’s routing system will catch it and throw a 404 for us. You could, of course, catch that particular exception and handle it, if you’d like. Get many get() works with Eloquent just like it does in normal query builder calls—build a query and call get() at the end to get the results: $vipContacts = Contact::where(‘vip’, true)->get(); However, there is an Eloquent-only method, all(), which you’ll often see people use when they want to get an unfiltered list of all data in the table: $contacts = Contact::all(); Using get() Instead of all() Any time you can use all(), you could use get(). Contact::get() has the same response as Contact::all(). However, the moment you start modifying your query—adding a where() filter, for exam‐ ple—all() will no longer work, but get() will continue working. So, even though all() is very common, I’d recommend using get() for everything, and ignoring the fact that all() even exists. The other thing that’s different about Eloquent’s get() method (versus all()) is that, prior to Laravel 5.3, it returned an array of models instead of a collection. In 5.3 and later, they both return collections. Introduction to Eloquent | 121 Chunking responses with chunk() If you’ve ever needed to process a large amount (thousands or more) of records at a time, you may have run into memory or locking issues. Laravel makes it possible to break your requests into smaller pieces (chunks) and process them in batches, keep‐ ing the memory load of your large request smaller. Example 5-20 illustrates the use of chunk() to split a query into “chunks” of 100 records each. Example 5-20. Chunking an Eloquent query to limit memory usage Contact::chunk(100, function ($contacts) { foreach ($contacts as $contact) { // Do something with $contact } }); Aggregates The aggregates that are available on the query builder are available on Eloquent quer‐ ies as well. For example: $countVips = Contact::where(‘vip’, true)->count(); $sumVotes = Contact::sum(‘votes’); $averageSkill = User::avg(‘skill_level’); Inserts and Updates with Eloquent Inserting and updating values is one of the places where Eloquent starts to diverge from normal query builder syntax. Inserts There are two primary ways to insert a new record using Eloquent. First, you can create a new instance of your Eloquent class, set your properties man‐ ually, and call save() on that instance, like in Example 5-21. Example 5-21. Inserting an Eloquent record by creating a new instance $contact = new Contact; $contact->name = ‘Ken Hirata’; $contact->email = ‘ken@hirata.com’; $contact->save(); // or $contact = new Contact([ ‘name’ => ‘Ken Hirata’, ’email’ => ‘ken@hirata.com’, 122 | Chapter 5: Databases and Eloquent ]); $contact->save(); // or $contact = Contact::make([ ‘name’ => ‘Ken Hirata’, ’email’ => ‘ken@hirata.com’, ]); $contact->save(); Until you save(), this instance of Contact represents the contact fully—except it has never been saved to the database. That means it doesn’t have an id, if the application quits it won’t persist, and it doesn’t have its created_at and updated_at values set. You can also pass an array to Model::create(), as shown in Example 5-22. Unlike make(), create() saves the instance to the database as soon as it’s called. Example 5-22. Inserting an Eloquent record by passing an array to create() $contact = Contact::create([ ‘name’ => ‘Keahi Hale’, ’email’ => ‘halek481@yahoo.com’, ]); Also be aware that in any context where you are passing an array (to new Model(), Model::make(), Model::create(), or Model::update()), every property you set via Model::create() has to be approved for “mass assignment,” which we’ll cover shortly. This is not necessary with the first example in Example 5-21, where you assign each property individually. Note that if you’re using Model::create(), you don’t need to save() the instance— that’s handled as a part of the model’s create() method. Updates Updating records looks very similar to inserting. You can get a specific instance, change its properties, and then save, or you can make a single call and pass an array of updated properties. Example 5-23 illustrates the first approach. Example 5-23. Updating an Eloquent record by updating an instance and saving $contact = Contact::find(1); $contact->email = ‘natalie@parkfamily.com’; $contact->save(); Introduction to Eloquent | 123 Since this record already exists, it will already have a created_at timestamp and an id, which will stay the same, but the updated_at field will be changed to the current date and time. Example 5-24 illustrates the second approach. Example 5-24. Updating one or more Eloquent records by passing an array to the update() method Contact::where(‘created_at’, ‘subYear()) ->update([‘longevity’ => ‘ancient’]); // or $contact = Contact::find(1); $contact->update([‘longevity’ => ‘ancient’]); This method expects an array where each key is the column name and each value is the column value. Mass assignment We’ve looked at a few examples of how to pass arrays of values into Eloquent class methods. However, none of these will actually work until you define which fields are “fillable” on the model. The goal of this is to protect you from (possibly malicious) user input accidentally setting new values on fields you don’t want changed. Consider the common scenario in Example 5-25. Example 5-25. Updating an Eloquent model using the entirety of a request’s input // ContactController public function update(Contact $contact, Request $request) { $contact->update($request->all()); } If you’re not familiar with the Illuminate Request object, Example 5-25 will take every piece of user input and pass it to the update() method. That all() method includes things like URL parameters and form inputs, so a malicious user could easily add some things in there, like id and owner_id, that you likely don’t want updated. Thankfully, that won’t actually work until you define your model’s fillable fields. You can either whitelist the fillable fields, or blacklist the “guarded” fields to determine which fields can or cannot be edited via “mass assignment”—that is, by passing an array of values into either create() or update(). Note that nonfillable properties can 124 | Chapter 5: Databases and Eloquent still be changed by direct assignment (e.g., $contact->password = ‘abc’;). Example 5-26 shows both approaches. Example 5-26. Using Eloquent’s llable or guarded properties to dene mass-assignable elds class Contact { protected $fillable = [‘name’, ’email’]; // or protected $guarded = [‘id’, ‘created_at’, ‘updated_at’, ‘owner_id’]; } Using Request::only() with Eloquent Mass Assignment In Example 5-25, we needed Eloquent’s mass assignment guard because we were using the all() method on the Request object to pass in the entirety of the user input. Eloquent’s mass assignment protection is a great tool here, but there’s also a helpful trick to keep you from accepting any old input from the user. The Request class has an only() method that allows you to pluck only a few keys from the user input. So now you can do this: Contact::create($request->only(‘name’, ’email’)); rstOrCreate() and rstOrNew() Sometimes you want to tell your application, “Get me an instance with these proper‐ ties, or if it doesn’t exist, create it.” This is where the firstOr*() methods come in. The firstOrCreate() and firstOrNew() methods take an array of keys and values as their first parameter: $contact = Contact::firstOrCreate([’email’ => ‘luis.ramos@myacme.com’]); They’ll both look for and retrieve the first record matching those parameters, and if there are no matching records, they’ll create an instance with those properties; firstOrCreate() will persist that instance to the database and then return it, while firstOrNew() will return it without saving it. If you pass an array of values as the second parameter, those values will be added to the created entry (if it’s created) but won’t be used to look up whether the entry exists. Introduction to Eloquent | 125 Deleting with Eloquent Deleting with Eloquent is very similar to updating with Eloquent, but with (optional) soft deletes, you can archive your deleted items for later inspection or even recovery. Normal deletes The simplest way to delete a model record is to call the delete() method on the instance itself: $contact = Contact::find(5); $contact->delete(); However, if you only have the ID, there’s no reason to look up an instance just to delete it; you can pass an ID or an array of IDs to the model’s destroy() method to delete them directly: Contact::destroy(1); // or Contact::destroy([1, 5, 7]); Finally, you can delete all of the results of a query: Contact::where(‘updated_at’, ‘subYear())->delete(); Soft deletes Soft deletes mark database rows as deleted without actually deleting them from the database. This gives you the ability to inspect them later, to have records that show more than “no information, deleted” when displaying historic information, and to allow your users (or admins) to restore some or all data. The hard part about handcoding an application with soft deletes is that every query you ever write will need to exclude the soft-deleted data. Thankfully, if you use Elo‐ quent’s soft deletes, every query you ever make will be scoped to ignore soft deletes by default, unless you explicitly ask to bring them back. Eloquent’s soft delete functionality requires a deleted_at column to be added to the table. Once you enable soft deletes on that Eloquent model, every query you ever write (unless you explicitly include soft-deleted records) will be scoped to ignore softdeleted rows. When Should I Use Soft Deletes? Just because a feature exists, it doesn’t mean you should always use it. Many folks in the Laravel community default to using soft deletes on every project just because the feature is there. There are real costs to soft deletes, though. It’s pretty likely that, if you view your database directly in a tool like Sequel Pro, you’ll forget to check the 126 | Chapter 5: Databases and Eloquent deleted_at column at least once. And if you don’t clean up old soft-deleted records, your databases will get larger and larger. Here’s my recommendation: don’t use soft deletes by default. Instead, use them when you need them, and when you do, clean out old soft deletes as aggressively as you can using a tool like Quicksand. The soft delete feature is a powerful tool, but not worth using unless you need it. Enabling soft deletes. You enable soft deletes by doing three things: adding the deleted_at column in a migration, importing the SoftDeletes trait in the model, and adding the deleted_at column to your $dates property. There’s a softDeletes() method available on the schema builder to add the deleted_at col‐ umn to a table, as you can see in Example 5-27. And Example 5-28 shows an Elo‐ quent model with soft deletes enabled. Example 5-27. Migration to add the soft delete column to a table Schema::table(‘contacts’, function (Blueprint $table) { $table->softDeletes(); }); Example 5-28. An Eloquent model with soft deletes enabled get(); Introduction to Eloquent | 127 Next, you can use the trashed() method to see if a particular instance has been softdeleted: if ($contact->trashed()) { // do something } Finally, you can get only soft-deleted items: $deletedContacts = Contact::onlyTrashed()->get(); Restoring soft-deleted entities. If you want to restore a soft-deleted item, you can run restore() on an instance or a query: $contact->restore(); // or Contact::onlyTrashed()->where(‘vip’, true)->restore(); Force-deleting soft-deleted entities. You can delete a soft-deleted entity by calling forceDelete() on an entity or query: $contact->forceDelete(); // or Contact::onlyTrashed()->forceDelete(); Scopes We’ve covered “filtered” queries, meaning any query where we’re not just returning every result for a table. But every time we’ve written them so far in this chapter, it’s been a manual process using the query builder. Local and global scopes in Eloquent allow you to define prebuilt “scopes” (filters) that you can use either every time a model is queried (“global”) or every time you query it with a particular method chain (“local”). Local scopes Local scopes are the simplest to understand. Let’s take this example: $activeVips = Contact::where(‘vip’, true)->where(‘trial’, false)->get(); First of all, if we write this combination of query methods over and over, it will get tedious. But additionally, the knowledge of how to define someone being an “active VIP” is now spread around our application. We want to centralize that knowledge. What if we could just write this? $activeVips = Contact::activeVips()->get(); 128 | Chapter 5: Databases and Eloquent We can—it’s called a local scope. And it’s easy to define on the Contact class, as you can see in Example 5-29. Example 5-29. Dening a local scope on a model class Contact { public function scopeActiveVips($query) { return $query->where(‘vip’, true)->where(‘trial’, false); } To define a local scope, we add a method to the Eloquent class that begins with “scope” and then contains the title-cased version of the scope name. This method is passed a query builder and needs to return a query builder, but of course you can modify the query before returning—that’s the whole point. You can also define scopes that accept parameters, as shown in Example 5-30. Example 5-30. Passing parameters to scopes class Contact { public function scopeStatus($query, $status) { return $query->where(‘status’, $status); } And you use them in the same way, just passing the parameter to the scope: $friends = Contact::status(‘friend’)->get(); Global scopes Remember how we talked about soft deletes only working if you scope every query on the model to ignore the soft-deleted items? That’s a global scope. And we can define our own global scopes, which will be applied on every query made from a given model. There are two ways to define a global scope: using a closure or using an entire class. In each, you’ll register the defined scope in the model’s boot() method. Let’s start with the closure method, illustrated in Example 5-31. Example 5-31. Adding a global scope using a closure … class Contact extends Model { Introduction to Eloquent | 129 protected static function boot() { parent::boot(); static::addGlobalScope(‘active’, function (Builder $builder) { $builder->where(‘active’, true); }); } That’s it. We just added a global scope named active, and now every query on this model will be scoped to only rows with active set to true. Next, let’s try the longer way, as shown in Example 5-32. Create a class that imple‐ ments Illuminate\Database\Eloquent\Scope, which means it will have an apply() method that takes an instance of a query builder and an instance of the model. Example 5-32. Creating a global scope class where(‘active’, true); } } To apply this scope to a model, once again override the parent’s boot() method and call addGlobalScope() on the class using static, as shown in Example 5-33. Example 5-33. Applying a class-based global scope get(); If you’re removing a single class-based global scope, you can pass the class name to withoutGlobalScope() or withoutGlobalScopes(): Contact::withoutGlobalScope(ActiveScope::class)->get(); Contact::withoutGlobalScopes([ActiveScope::class, VipScope::class])->get(); Or, you can just disable all global scopes for a query: Contact::withoutGlobalScopes()->get(); Customizing Field Interactions with Accessors, Mutators, and Attribute Casting Now that we’ve covered how to get records into and out of the database with Elo‐ quent, let’s talk about decorating and manipulating the individual attributes on your Eloquent models. Accessors, mutators, and attribute casting all allow you to customize the way individ‐ ual attributes of Eloquent instances are input or output. Without using any of these, each attribute of your Eloquent instance is treated like a string, and you can’t have any attributes on your models that don’t exist on the database. But we can change that. Accessors Accessors allow you to define custom attributes on your Eloquent models for when you are reading data from the model instance. This may be because you want to change how a particular column is output, or because you want to create a custom attribute that doesn’t exist in the database table at all. Introduction to Eloquent | 131 You define an accessor by writing a method on your model with the following struc‐ ture: get{PascalCasedPropertyName}Attribute. So, if your property name is first_name, the accessor method would be named getFirstNameAttribute. Let’s try it out. First, we’ll decorate a preexisting column (Example 5-34). Example 5-34. Decorating a preexisting column using Eloquent accessors // Model definition: class Contact extends Model { public function getNameAttribute($value) { return $value ?: ‘(No name provided)’; } } // Accessor usage: $name = $contact->name; But we can also use accessors to define attributes that never existed in the database, as seen in Example 5-35. Example 5-35. Dening an attribute with no backing column using Eloquent accessors // Model definition: class Contact extends Model { public function getFullNameAttribute() { return $this->first_name . ‘ ‘ . $this->last_name; } } // Accessor usage: $fullName = $contact->full_name; Mutators Mutators work the same way as accessors, except they’re for determining how to pro‐ cess setting the data instead of getting it. Just like with accessors, you can use them to modify the process of writing data to existing columns, or to allow for setting col‐ umns that don’t exist in the database. You define a mutator by writing a method on your model with the following struc‐ ture: set{PascalCasedPropertyName}Attribute. So, if your property name is first_name, the mutator method would be named setFirstNameAttribute. 132 | Chapter 5: Databases and Eloquent Let’s try it out. First, we’ll add a constraint to updating a preexisting column (Example 5-36). Example 5-36. Decorating setting the value of an attribute using Eloquent mutators // Defining the mutator class Order extends Model { public function setAmountAttribute($value) { $this->attributes[‘amount’] = $value > 0 ? $value : 0; } } // Using the mutator $order->amount = ’15’; This reveals that the way mutators are expected to “set” data on the model is by set‐ ting it in $this->attributes with the column name as the key. Now let’s add a proxy column for setting, as shown in Example 5-37. Example 5-37. Allowing for setting the value of a nonexistent attribute using Eloquent mutators // Defining the mutator class Order extends Model { public function setWorkgroupNameAttribute($workgroupName) { $this->attributes[’email’] = “{$workgroupName}@ourcompany.com”; } } // Using the mutator $order->workgroup_name = ‘jstott’; As you can probably guess, it’s relatively uncommon to create a mutator for a nonexistent column, because it can be confusing to set one property and have it change a different column—but it is possible. Attribute casting You can probably imagine writing accessors to cast all of your integer-type fields as integers, encode and decode JSON to store in a TEXT column, or convert TINYINT 0 and 1 to and from Boolean values. Thankfully, there’s a system for that in Eloquent already. It’s called attribute casting, and it allows you to define that any of your columns should always be treated, both Introduction to Eloquent | 133 on read and on write, as if they are of a particular data type. The options are listed in Table 5-1. Table 5-1. Possible attribute casting column types Type Description int|integer Casts with PHP (int) real|float|double Casts with PHP (float) string Casts with PHP (string) bool|boolean Casts with PHP (bool) object Parses to/from JSON, as a stdClass object array Parses to/from JSON, as an array collection Parses to/from JSON, as a collection date|datetime Parses from database DATETIME to Carbon, and back timestamp Parses from database TIMESTAMP to Carbon, and back Example 5-38 shows how you use attribute casting in your model. Example 5-38. Using attribute casting on an Eloquent model class Contact { protected $casts = [ ‘vip’ => ‘boolean’, ‘children_names’ => ‘array’, ‘birthday’ => ‘date’, ]; } Date mutators You can choose for particular columns to be mutated as timestamp columns by adding them to the dates array, as seen in Example 5-39. Example 5-39. Dening columns to be mutated as timestamps class Contact { protected $dates = [ ‘met_at’, ]; } By default, this array contains created_at and updated_at, so adding entries to dates just adds them to the list. 134 | Chapter 5: Databases and Eloquent However, there’s no difference between adding columns to this list and adding them to $this->casts as timestamp, so this is becoming a bit of an unnecessary feature now that attribute casting can cast timestamps (new since Laravel 5.2). Eloquent Collections When you make any query call in Eloquent that has the potential to return multiple rows, instead of an array they’ll come packaged in an Eloquent collection, which is a specialized type of collection. Let’s take a look at collections and Eloquent collections, and what makes them better than plain arrays. Introducing the base collection Laravel’s Collection objects (Illuminate\Support\Collection) are a little bit like arrays on steroids. The methods they expose on array-like objects are so helpful that, once you’ve been using them for a while, you’ll likely want to pull them into nonLaravel projects—which you can, with the Tightenco/Collect package. The simplest way to create a collection is to use the collect() helper. Either pass an array in, or use it without arguments to create an empty collection and then push items into it later. Let’s try it: $collection = collect([1, 2, 3]); Now let’s say we want to filter out any even numbers: $odds = $collection->reject(function ($item) { return $item % 2 === 0; }); Or what if we want to get a version of the collection where each item is multiplied by 10? We can do that as follows: $multiplied = $collection->map(function ($item) { return $item * 10; }); We can even get only the even numbers, multiply them all by 10, and reduce them to a single number by sum(): $sum = $collection ->filter(function ($item) { return $item % 2 == 0; })->map(function ($item) { return $item * 10; })->sum(); As you can see, collections provide a series of methods, which can optionally be chained, to perform functional operations on your arrays. They provide the same functionality as native PHP methods like array_map() and array_reduce(), but you Introduction to Eloquent | 135 don’t have to memorize PHP’s unpredictable parameter order, and the method chain‐ ing syntax is infinitely more readable. There are more than 60 methods available on the Collection class, including meth‐ ods like max(), whereIn(), flatten(), and flip(), and there’s not enough space to cover them all here. We’ll talk about more in Chapter 17, or you can check out the Laravel collections docs to see all of the methods. Collections in the Place of Arrays Collections can also be used in any context (except typehinting) where you can use arrays; they allow for iteration, so you can pass them to foreach, and they allow for array access, so if they’re keyed you can try $a = $collection[‘a’]. What Eloquent collections add Each Eloquent collection is a normal collection, but extended for the particular needs of a collection of Eloquent results. Once again, there’s not enough room here to cover all of the additions, but they’re centered around the unique aspects of interacting with a collection not just of generic objects, but objects meant to represent database rows. For example, every Eloquent collection has a method called modelKeys() that returns an array of the primary keys of every instance in the collection. find($id) looks for an instance that has the primary key of $id. One additional feature available here is the ability to define that any given model should return its results wrapped in a specific class of collection. So, if you want to add specific methods to any collection of objects of your Order model—possibly related to summarizing the financial details of your orders—you could create a cus‐ tom OrderCollection that extends Illuminate\Database\Eloquent\Collection, and then register it in your model, as shown in Example 5-40. Example 5-40. Custom Collection classes for Eloquent models … class OrderCollection extends Collection { public function sumBillableAmount() { return $this->reduce(function ($carry, $order) { return $carry + ($order->billable ? $order->amount : 0); }, 0); } } 136 | Chapter 5: Databases and Eloquent … class Order extends Model { public function newCollection(array $models = []) { return new OrderCollection($models); } Now, any time you get back a collection of Orders (e.g., from Order::all()), it’ll actually be an instance of the OrderCollection class: $orders = Order::all(); $billableAmount = $orders->sumBillableAmount(); Eloquent Serialization Serialization is what happens when you take something complex—an array, or an object—and convert it to a string. In a web-based context, that string is often JSON, but it could take other forms as well. Serializing complex database records can be, well, complex, and this is one of the places many ORMs fall short. Thankfully, you get two powerful methods for free with Eloquent: toArray() and toJson(). Collections also have toArray() and toJson(), so all of these are valid: $contactArray = Contact::first()->toArray(); $contactJson = Contact::first()->toJson(); $contactsArray = Contact::all()->toArray(); $contactsJson = Contact::all()->toJson(); You can also cast an Eloquent instance or collection to a string ($string = (string) $contact;), but both models and collections will just run toJson() and return the result. Returning models directly from route methods Laravel’s router eventually converts everything routes return to a string, so there’s a clever trick you can use. If you return the result of an Eloquent call in a controller, it will be automatically cast to a string, and therefore returned as JSON. That means a JSON-returning route can be as simple as either of the ones in Example 5-41. Example 5-41. Returning JSON from routes directly // routes/web.php Route::get(‘api/contacts’, function () { return Contact::all(); }); Introduction to Eloquent | 137 Route::get(‘api/contacts/{id}’, function ($id) { return Contact::findOrFail($id); }); Hiding attributes from JSON It’s very common to use JSON returns in APIs, and it’s very common to want to hide certain attributes in these contexts, so Eloquent makes it easy to hide any attributes every time you cast to JSON. You can either blacklist attributes, hiding the ones you list: class Contact extends Model { public $hidden = [‘password’, ‘remember_token’]; or whitelist attributes, showing only the ones you list: class Contact extends Model { public $visible = [‘name’, ’email’, ‘status’]; This also works for relationships: class User extends Model { public $hidden = [‘contacts’]; public function contacts() { return $this->hasMany(Contact::class); } Loading the Contents of a Relationship By default, the contents of a relationship are not loaded when you get a database record, so it doesn’t matter whether you hide them or not. But, as you’ll learn shortly, it’s possible to get a record with its related items, and in this context, those items will not be included in a serialized copy of that record if you choose to hide that relationship. In case you’re curious now, you can get a User with all contacts— assuming you’ve set up the relationship correctly—with the follow‐ ing call: $user = User::with(‘contacts’)->first(); There might be times when you want to make an attribute visible just for a single call. That’s possible, with the Eloquent method makeVisible(): $array = $user->makeVisible(‘remember_token’)->toArray(); 138 | Chapter 5: Databases and Eloquent Adding a Generated Column to Array and JSON Output If you have created an accessor for a column that doesn’t exist—for example, our full_name column from Example 5-35—add it to the $appends array on the model to add it to the array and JSON output: class Contact extends Model { protected $appends = [‘full_name’]; public function getFullNameAttribute() { return “{$this->first_name} {$this->last_name}”; } } Eloquent Relationships In a relational database model, it’s expected that you will have tables that are related to each other—hence the name. Eloquent provides simple and powerful tools to make the process of relating your database tables easier than ever before. Many of our examples in this chapter have been centered around a user who has many contacts, a relatively common situation. In an ORM like Eloquent, you would call this a one-to-many relationship: the one user has many contacts. If it was a CRM where a contact could be assigned to many users, then this would be a many-to-many relationship: many users can be related to one contact, and each user can be related to many contacts. A user has and belongs to many contacts. If each contact can have many phone numbers, and a user wanted a database of every phone number for their CRM, you would say the user has many phone numbers through contacts—that is, a user has many contacts, and the contact has many phone numbers, so the contact is sort of an intermediary. And what if each contact has an address, but you’re only interested in tracking one address? You could have all the address fields on the Contact, but you might also cre‐ ate an Address model—meaning the contact has one address. Finally, what if you want to be able to star (favorite) contacts, but also events? This would be a polymorphic relationship, where a user has many stars, but some may be contacts and some may be events. So, let’s dig into how to define and access these relationships. Introduction to Eloquent | 139 One to one Let’s start simple: a Contact has one PhoneNumber. This relationship is defined in Example 5-42. Example 5-42. Dening a one-to-one relationship class Contact extends Model { public function phoneNumber() { return $this->hasOne(PhoneNumber::class); } As you can tell, the methods defining relationships are on the Eloquent model itself ($this->hasOne()) and take, at least in this instance, the fully qualified class name of the class that you’re relating them to. How should this be defined in your database? Since we’ve defined that the Contact has one PhoneNumber, Eloquent expects that the table supporting the PhoneNumber class (likely phone_numbers) has a contact_id column on it. If you named it some‐ thing different (for instance, owner_id), you’ll need to change your definition: return $this->hasOne(PhoneNumber::class, ‘owner_id’); Here’s how we access the PhoneNumber of a Contact: $contact = Contact::first(); $contactPhone = $contact->phoneNumber; Notice that we define the method in Example 5-42 with phoneNumber(), but we access it with ->phoneNumber. That’s the magic. You could also access it with ->phone_number. This will return a full Eloquent instance of the related PhoneNumber record. But what if we want to access the Contact from the PhoneNumber? There’s a method for that, too (see Example 5-43). Example 5-43. Dening a one-to-one relationship’s inverse class PhoneNumber extends Model { public function contact() { return $this->belongsTo(Contact::class); } Then we access it the same way: $contact = $phoneNumber->contact; 140 | Chapter 5: Databases and Eloquent Inserting Related Items Each relationship type has its own quirks for how to relate models, but here’s the core of how it works: pass an instance to save(), or an array of instances to saveMany(). You can also pass properties to create() or createMany() and they’ll make new instances for you: $contact = Contact::first(); $phoneNumber = new PhoneNumber; $phoneNumber->number = 8008675309; $contact->phoneNumbers()->save($phoneNumber); // or $contact->phoneNumbers()->saveMany([ PhoneNumber::find(1), PhoneNumber::find(2), ]); // or $contact->phoneNumbers()->create([ ‘number’ => ‘+13138675309’, ]); // or $contact->phoneNumbers()->createMany([ [‘number’ => ‘+13138675309’], [‘number’ => ‘+15556060842’], ]); The createMany() method is only available in Laravel 5.4 and later. One to many The one-to-many relationship is by far the most common. Let’s take a look at how to define that our User has many Contacts (Example 5-44). Example 5-44. Dening a one-to-many relationship class User extends Model { public function contacts() { return $this->hasMany(Contact::class); } Introduction to Eloquent | 141 Once again, this expects that the Contact model’s backing table (likely contacts) has a user_id column on it. If it doesn’t, override it by passing the correct column name as the second parameter of hasMany(). We can get a User’s Contacts as follows: $user = User::first(); $usersContacts = $user->contacts; Just like with one to one, we use the name of the relationship method and call it as if it were a property instead of a method. However, this method returns a collection instead of a model instance. And this is a normal Eloquent collection, so we can have all sorts of fun with it: $donors = $user->contacts->filter(function ($contact) { return $contact->status == ‘donor’; }); $lifetimeValue = $contact->orders->reduce(function ($carry, $order) { return $carry + $order->amount; }, 0); Just like with one to one, we can also define the inverse (Example 5-45). Example 5-45. Dening a one-to-many relationship’s inverse class Contact extends Model { public function user() { return $this->belongsTo(User::class); } And just like with one to one, we can access the User from the Contact: $userName = $contact->user->name; 142 | Chapter 5: Databases and Eloquent Attaching and Detaching Related Items from the Attached Item Most of the time we attach an item by running save() on the par‐ ent and passing in the related item, as in $user->contacts()->save($contact). But if you want to perform these behaviors on the attached (“child”) item, you can use associate() and dissociate() on the method that returns the belongsTo relationship: $contact = Contact::first(); $contact->user()->associate(User::first()); $contact->save(); // and later $contact->user()->dissociate(); $contact->save(); Using relationships as query builders. Until now, we’ve taken the method name (e.g., contacts()) and called it as if were a property (e.g., $user->contacts). What hap‐ pens if we call it as a method? Instead of processing the relationship, it will return a pre-scoped query builder. So if you have User 1, and you call its contacts() method, you will now have a query builder prescoped to “all contacts that have a field user_id with the value of 1.” You can then build out a functional query from there: $donors = $user->contacts()->where(‘status’, ‘donor’)->get(); Selecting only records that have a related item. You can choose to select only records that meet particular criteria with regard to their related items using has(): $postsWithComments = Post::has(‘comments’)->get(); You can also adjust the criteria further: $postsWithManyComments = Post::has(‘comments’, ‘>=’, 5)->get(); You can nest the criteria: $usersWithPhoneBooks = User::has(‘contacts.phoneNumbers’)->get(); And finally, you can write custom queries on the related items: // Gets all contacts with a phone number containing the string “867-5309” $jennyIGotYourNumber = Contact::whereHas(‘phoneNumbers’, function ($query) { $query->where(‘number’, ‘like’, ‘%867-5309%’); }); Introduction to Eloquent | 143 Has many through hasManyThrough() is really a convenience method for pulling in relationships of a relationship. Think of the example I gave earlier, where a User has many Contacts and each Contact has many PhoneNumbers. What if you want to get a user’s list of contact phone numbers? That’s has-many-through relation. This structure assumes that your contacts table has a user_id to relate the contacts to the users and the phone_numbers table has a contact_id to relate it to the contacts. Then, we define the relationship on the User as in Example 5-46. Example 5-46. Dening a has-many-through relationship class User extends Model { public function phoneNumbers() { return $this->hasManyThrough(PhoneNumber::class, Contact::class); } You’d access this relationship using $user->phone_numbers, and as always you can customize the relationship key on the intermediate model (with the third parameter of hasManyThrough()) and the relationship key on the distant model (with the fourth parameter). Has one through hasOneThrough() is just like hasManyThrough(), but instead of accessing many related items through intermediate items, you’re only accessing a single related item through a single intermediate item. What if each user belonged to a company, and that company had a single phone num‐ ber, and you wanted to be able to get a user’s phone number by pulling their compa‐ ny’s phone number? That’s hasOneThrough(). Example 5-47. Dening a has-one-through relationship class User extends Model { public function phoneNumber() { return $this->hasOneThrough(PhoneNumber::class, Company::class); } 144 | Chapter 5: Databases and Eloquent Many to many This is where things start to get complex. Let’s take our example of a CRM that allows a User to have many Contacts, and each Contact to be related to multiple Users. First, we define the relationship on the User as in Example 5-48. Example 5-48. Dening a many-to-many relationship class User extends Model { public function contacts() { return $this->belongsToMany(Contact::class); } } And since this is many to many, the inverse looks exactly the same (Example 5-49). Example 5-49. Dening a many-to-many relationship’s inverse class Contact extends Model { public function users() { return $this->belongsToMany(User::class); } } Since a single Contact can’t have a user_id column and a single User can’t have a contact_id column, many-to-many relationships rely on a pivot table that connects the two. The conventional naming of this table is done by placing the two singular table names together, ordered alphabetically, and separating them by an underscore. So, since we’re linking users and contacts, our pivot table should be named contacts_users (if you’d like to customize the table name, pass it as the second parameter to the belongsToMany() method). It needs two columns: contact_id and user_id. Just like with hasMany(), we get access to a collection of the related items, but this time it’s from both sides (Example 5-50). Example 5-50. Accessing the related items from both sides of a many-to-many relationship $user = User::first(); $user->contacts->each(function ($contact) { Introduction to Eloquent | 145 // do something }); $contact = Contact::first(); $contact->users->each(function ($user) { // do something }); $donors = $user->contacts()->where(‘status’, ‘donor’)->get(); Getting data from the pivot table. One thing that’s unique about many to many is that it’s our first relationship that has a pivot table. The less data you have in a pivot table, the better, but there are some cases where it’s valuable to store information in your pivot table—for example, you might want to store a created_at field to see when this relationship was created. In order to store these fields, you have to add them to the relationship definition, like in Example 5-51. You can define specific fields using withPivot() or add created_at and updated_at timestamps using withTimestamps(). Example 5-51. Adding elds to a pivot record public function contacts() { return $this->belongsToMany(Contact::class) ->withTimestamps() ->withPivot(‘status’, ‘preferred_greeting’); } When you get a model instance through a relationship, it will have a pivot property on it, which will represent its place in the pivot table you just pulled it from. So, you can do something like Example 5-52. Example 5-52. Getting data from a related item’s pivot entry $user = User::first(); $user->contacts->each(function ($contact) { echo sprintf( ‘Contact associated with this user at: %s’, $contact->pivot->created_at ); }); If you’d like, you can customize the pivot key to have a different name using the as() method, as shown in Example 5-53. 146 | Chapter 5: Databases and Eloquent Example 5-53. Customizing the pivot attribute name // User model public function groups() { return $this->belongsToMany(Group::class) ->withTimestamps() ->as(‘membership’); } // Using this relationship: User::first()->groups->each(function ($group) { echo sprintf( ‘User joined this group at: %s’, $group->membership->created_at ); }); Unique Aspects of Attaching and Detaching Many-to-Many Related Items Since your pivot table can have its own properties, you need to be able to set those properties when you’re attaching a many-to-many related item. You can do that by passing an array as the second parameter to save(): $user = User::first(); $contact = Contact::first(); $user->contacts()->save($contact, [‘status’ => ‘donor’]); Additionally, you can use attach() and detach() and, instead of passing in an instance of a related item, you can just pass an ID. They work just the same as save() but can also accept an array of IDs without you needing to rename the method to something like attachMany(): $user = User::first(); $user->contacts()->attach(1); $user->contacts()->attach(2, [‘status’ => ‘donor’]); $user->contacts()->attach([1, 2, 3]); $user->contacts()->attach([ 1 => [‘status’ => ‘donor’], 2, 3, ]); $user->contacts()->detach(1); $user->contacts()->detach([1, 2]); $user->contacts()->detach(); // Detaches all contacts If your goal is not to attach or detach, but instead just to invert whatever the current attachment state is, you want the toggle() method. When you use toggle(), if the Introduction to Eloquent | 147 given ID is currently attached, it will be detached; and if it is currently detached, it will be attached: $user->contacts()->toggle([1, 2, 3]); You can also use updateExistingPivot() to make changes just to the pivot record: $user->contacts()->updateExistingPivot($contactId, [ ‘status’ => ‘inactive’, ]); And if you’d like to replace the current relationships, effectively detaching all previous relationships and attaching new ones, you can pass an array to sync(): $user->contacts()->sync([1, 2, 3]); $user->contacts()->sync([ 1 => [‘status’ => ‘donor’], 2, 3, ]); Polymorphic Remember, our polymorphic relationship is where we have multiple Eloquent classes corresponding to the same relationship. We’re going to use Stars (like favorites) right now. A user can star both Contacts and Events, and that’s where the name polymor‐ phic comes from: there’s a single interface to objects of multiple types. So, we’ll need three tables, and three models: Star, Contact, and Event (four of each, technically, because we’ll need users and User, but we’ll get there in a second). The contacts and events tables will just be as they normally are, and the stars table will contain id, starrable_id, and starrable_type fields. For each Star, we’ll be defin‐ ing which “type” (e.g., Contact or Event) and which ID of that type (e.g., 1) it is. Let’s create our models, as seen in Example 5-54. Example 5-54. Creating the models for a polymorphic starring system class Star extends Model { public function starrable() { return $this->morphTo(); } } class Contact extends Model { public function stars() { 148 | Chapter 5: Databases and Eloquent return $this->morphMany(Star::class, ‘starrable’); } } class Event extends Model { public function stars() { return $this->morphMany(Star::class, ‘starrable’); } } So, how do we create a Star? $contact = Contact::first(); $contact->stars()->create(); It’s that easy. The Contact is now starred. In order to find all of the Stars on a given Contact, we call the stars() method like in Example 5-55. Example 5-55. Retrieving the instances of a polymorphic relationship $contact = Contact::first(); $contact->stars->each(function ($star) { // Do stuff }); If we have an instance of Star, we can get its target by calling the method we used to define its morphTo relationship, which in this context is starrable(). Take a look at Example 5-56. Example 5-56. Retrieving the target of a polymorphic instance $stars = Star::all(); $stars->each(function ($star) { var_dump($star->starrable); // An instance of Contact or Event }); Finally, you might be wondering, “What if I want to know who starred this contact?” That’s a great question. It’s as simple as adding user_id to your stars table, and then setting up that a User has many Stars and a Star belongs to one User—a one-tomany relationship (Example 5-57). The stars table becomes almost a pivot table between your User and your Contacts and Events. Introduction to Eloquent | 149 Example 5-57. Extending a polymorphic system to differentiate by user class Star extends Model { public function starrable() { return $this->morphsTo; } public function user() { return $this->belongsTo(User::class); } } class User extends Model { public function stars() { return $this->hasMany(Star::class); } } That’s it! You can now run $star->user or $user->stars to find a list of a User’s Stars or to find the starring User from a Star. Also, when you create a new Star, you’ll now want to pass the User: $user = User::first(); $event = Event::first(); $event->stars()->create([‘user_id’ => $user->id]); Many to many polymorphic The most complex and least common of the relationship types, many-to-many poly‐ morphic relationships are like polymorphic relationships, except instead of being one to many, they’re many to many. The most common example for this relationship type is the tag, so I’ll keep it safe and use that as our example. Let’s imagine you want to be able to tag Contacts and Events. The uniqueness of many-to-many polymorphism is that it’s many to many: each tag may be applied to multiple items, and each tagged item might have multiple tags. And to add to that, it’s polymorphic: tags can be related to items of several dif‐ ferent types. For the database, we’ll start with the normal structure of the polymor‐ phic relationship but also add a pivot table. This means we’ll need a contacts table, an events table, and a tags table, all shaped like normal with an ID and whatever properties you want, and a new taggables table, which will have tag_id, taggable_id, and taggable_type fields. Each entry into the taggables table will relate a tag with one of the taggable content types. 150 | Chapter 5: Databases and Eloquent Now let’s define this relationship on our models, as seen in Example 5-58. Example 5-58. Dening a polymorphic many-to-many relationship class Contact extends Model { public function tags() { return $this->morphToMany(Tag::class, ‘taggable’); } } class Event extends Model { public function tags() { return $this->morphToMany(Tag::class, ‘taggable’); } } class Tag extends Model { public function contacts() { return $this->morphedByMany(Contact::class, ‘taggable’); } public function events() { return $this->morphedByMany(Event::class, ‘taggable’); } } Here’s how to create your first tag: $tag = Tag::firstOrCreate([‘name’ => ‘likes-cheese’]); $contact = Contact::first(); $contact->tags()->attach($tag->id); We get the results of this relationship like normal, as seen in Example 5-59. Example 5-59. Accessing the related items from both sides of a many-to-many polymorphic relationship $contact = Contact::first(); $contact->tags->each(function ($tag) { // Do stuff }); $tag = Tag::first(); Introduction to Eloquent | 151 $tag->contacts->each(function ($contact) { // Do stuff }); Child Records Updating Parent Record Timestamps Remember, any Eloquent models by default will have created_at and updated_at timestamps. Eloquent will set the updated_at timestamp automatically any time you make any changes to a record. When a related item has a belongsTo or belongsToMany relationship with another item, it might be valuable to mark the other item as updated any time the related item is updated. For example, if a PhoneNumber is updated, maybe the Contact it’s connec‐ ted to should be marked as having been updated as well. We can accomplish this by adding the method name for that relationship to a $touches array property on the child class, as in Example 5-60. Example 5-60. Updating a parent record any time the child record is updated class PhoneNumber extends Model { protected $touches = [‘contact’]; public function contact() { return $this->belongsTo(Contact::class); } } Eager loading By default, Eloquent loads relationships using “lazy loading.” This means when you first load a model instance, its related models will not be loaded along with it. Rather, they’ll only be loaded once you access them on the model; they’re “lazy” and don’t do any work until called upon. This can become a problem if you’re iterating over a list of model instances and each has a related item (or items) that you’re working on. The problem with lazy loading is that it can introduce significant database load (often the N+1 problem, if you’re famil‐ iar with the term; if not, just ignore this parenthetical remark). For instance, every time the loop in Example 5-61 runs, it executes a new database query to look up the phone numbers for that Contact. 152 | Chapter 5: Databases and Eloquent Example 5-61. Retrieving one related item for each item in a list (N+1) $contacts = Contact::all(); foreach ($contacts as $contact) { foreach ($contact->phone_numbers as $phone_number) { echo $phone_number->number; } } If you are loading a model instance, and you know you’ll be working with its relation‐ ships, you can instead choose to “eager-load” one or many of its sets of related items: $contacts = Contact::with(‘phoneNumbers’)->get(); Using the with() method with a retrieval gets all of the items related to the pulled item(s); as you can see in this example, you pass it the name of the method the rela‐ tionship is defined by. When we use eager loading, instead of pulling the related items one at a time when they’re requested (e.g., selecting one contact’s phone numbers each time a foreach loop runs), we have a single query to pull the initial items (selecting all contacts) and a second query to pull all their related items (selecting all phone numbers owned by the contacts we just pulled). You can eager-load multiple relationships by passing multiple parameters to the with() call: $contacts = Contact::with(‘phoneNumbers’, ‘addresses’)->get(); And you can nest eager loading to eager-load the relationships of relationships: $authors = Author::with(‘posts.comments’)->get(); Constraining eager loads. If you want to eager-load a relationship, but not all of the items, you can pass a closure to with() to define exactly which related items to eager-load: $contacts = Contact::with([‘addresses’ => function ($query) { $query->where(‘mailable’, true); }])->get(); Lazy eager loading. I know it sounds crazy, because we just defined eager loading as sort of the opposite of lazy loading, but sometimes you don’t know you want to per‐ form an eager-load query until after the initial instances have been pulled. In this context, you’re still able to make a single query to look up all of the related items, avoiding N+1 cost. We call this “lazy eager loading”: $contacts = Contact::all(); Introduction to Eloquent | 153 if ($showPhoneNumbers) { $contacts->load(‘phoneNumbers’); } To load a relationship only when it has not already been loaded, use the loadMissing() method (available only since Laravel 5.5): $contacts = Contact::all(); if ($showPhoneNumbers) { $contacts->loadMissing(‘phoneNumbers’); } Eager loading only the count If you want to eager-load relationships but only so you can have access to the count of items in each relationship, you can try withCount(): $authors = Author::withCount(‘posts’)->get(); // Adds a “posts_count” integer to each Author with a count of that // author’s related posts Eloquent Events Eloquent models fire events out into the void of your application every time certain actions happen, regardless of whether you’re listening. If you’re familiar with pub/ sub, it’s this same model (you’ll learn more about Laravel’s entire event system in Chapter 16). Here’s a quick rundown of binding a listener to when a new Contact is created. We’re going to bind it in the boot() method of AppServiceProvider, and let’s imagine we’re notifying a third-party service every time we create a new Contact. Example 5-62. Binding a listener to an Eloquent event class AppServiceProvider extends ServiceProvider { public function boot() { $thirdPartyService = new SomeThirdPartyService; Contact::creating(function ($contact) use ($thirdPartyService) { try { $thirdPartyService->addContact($contact); } catch (Exception $e) { Log::error(‘Failed adding contact to ThirdPartyService; canceled.’); return false; // Cancels Eloquent create() } 154 | Chapter 5: Databases and Eloquent }); } We can see a few things in Example 5-62. First, we use Modelname::eventName() as the method, and pass it a closure. The closure gets access to the model instance that is being operated on. Second, we’re going to need to define this listener in a service pro‐ vider somewhere. And third, if we return false, the operation will cancel and the save() or update() will be canceled. Here are the events that every Eloquent model fires: • creating • created • updating • updated • saving • saved • deleting • deleted • restoring • restored • retrieved Most of these should be pretty clear, except possibly restoring and restored, which fire when you’re restoring a soft-deleted row. Also, saving is fired for both creating and updating and saved is fired for both created and updated. retrieved (available in Laravel 5.5 and later) is fired when an existing model is retrieved from the database. Testing Laravel’s entire application testing framework makes it easy to test your database— not by writing unit tests against Eloquent, but by just being willing to test your entire application. Take this scenario. You want to test to ensure that a particular page shows one contact but not another. Some of that logic has to do with the interplay between the URL and the controller and the database, so the best way to test it is an application test. You might be thinking about mocking Eloquent calls and trying to avoid the system hit‐ ting the database. Don’t do it. Try Example 5-63 instead. Testing | 155 Example 5-63. Testing database interactions with simple application tests public function test_active_page_shows_active_and_not_inactive_contacts() { $activeContact = factory(Contact::class)->create(); $inactiveContact = factory(Contact::class)->states(‘inactive’)->create(); $this->get(‘active-contacts’) ->assertSee($activeContact->name) ->assertDontSee($inactiveContact->name); } As you can see, model factories and Laravel’s application testing features are great for testing database calls. Alternatively, you can look for that record directly in the database, as in Example 5-64. Example 5-64. Using assertDatabaseHas() to check for certain records in the database public function test_contact_creation_works() { $this->post(‘contacts’, [ ’email’ => ‘jim@bo.com’ ]); $this->assertDatabaseHas(‘contacts’, [ ’email’ => ‘jim@bo.com’ ]); } Eloquent and Laravel’s database framework are tested extensively. You don’t need to test them. You don’t need to mock them. If you really want to avoid hitting the data‐ base, you can use a repository and then return unsaved instances of your Eloquent models. But the most important message is, test the way your application uses your database logic. If you have custom accessors, mutators, scopes, or whatever else, you can also test them directly, as in Example 5-65. Example 5-65. Testing accessors, mutators, and scopes public function test_full_name_accessor_works() { $contact = factory(Contact::class)->make([ ‘first_name’ => ‘Alphonse’, ‘last_name’ => ‘Cumberbund’ ]); 156 | Chapter 5: Databases and Eloquent $this->assertEquals(‘Alphonse Cumberbund’, $contact->fullName); } public function test_vip_scope_filters_out_non_vips() { $vip = factory(Contact::class)->states(‘vip’)->create(); $nonVip = factory(Contact::class)->create(); $vips = Contact::vips()->get(); $this->assertTrue($vips->contains(‘id’, $vip->id)); $this->assertFalse($vips->contains(‘id’, $nonVip->id)); } Just avoid writing tests that leave you creating complex “Demeter chains” to assert that a particular fluent stack was called on some database mock. If your testing starts to get overwhelming and complex around the database layer, it’s because you’re allow‐ ing preconceived notions to force you into unnecessarily complex systems. Keep it simple. Different Names for Testing Methods Prior to Laravel 5.4 In projects running versions of Laravel prior to 5.4, assertDatabaseHas() should be replaced by seeInDatabase(), get() should be replaced by visit(), assertSee() should be replaced by see(), and assertDontSee() should be replaced by dontSee(). TL;DR Laravel comes with a suite of powerful database tools, including migrations, seeding, an elegant query builder, and Eloquent, a powerful ActiveRecord ORM. Laravel’s database tools don’t require you to use Eloquent at all—you can access and manipu‐ late the database with a thin layer of convenience without having to write SQL directly. But adding an ORM, whether it’s Eloquent or Doctrine or whatever else, is easy and can work neatly with Laravel’s core database tools. Eloquent follows the Active Record pattern, which makes it simple to define a class of database-backed objects, including which table they’re stored in and the shape of their columns, accessors, and mutators. Eloquent can handle every sort of normal SQL action and also complex relationships, up to and including polymorphic manyto-many relationships. Laravel also has a robust system for testing databases, including model factories. TL;DR | 157 CHAPTER 6 Frontend Components Laravel is primarily a PHP framework, but it also has a series of components focused on generating frontend code. Some of these, like pagination and message bags, are PHP helpers that target the frontend, but Laravel also provides a Webpack-based build system called Mix and some conventions around non-PHP assets. Laravel’s Build Tools Before and After Laravel 5.4 Prior to Laravel 5.4, Laravel’s frontend build tool was named Elixer, and it was based on Gulp. In 5.4 and later, the new build tool is named Mix, and it’s based on Webpack. Since Mix is at the core of the non-PHP frontend components, let’s start there. Laravel Mix Mix is a build tool that provides a simple user interface and a series of conventions on top of Webpack. Mix’s core value proposition is simplifying the most common build and compilation Webpack tasks by means of a cleaner API and a series of naming and application structure conventions. A Quick Introduction to Webpack Webpack is a JavaScript tool designed for compiling static assets; the Webpack team describes its purpose as bundling “modules with dependencies” together and produc‐ ing “static assets.” Webpack is similar to Gulp or Grunt in that, like Webpack, those tools are often used for processing and bundling dependencies for websites. This will commonly include 159 running a CSS preprocessor like Sass or Less or PostCSS, copying files, and concate‐ nating and minifying JavaScript. Unlike the others, Webpack is specically focused on bundling together modules with dependencies and producing static assets as a result. Gulp and Grunt are task run‐ ners, which, like Make and Rake before them, can be used to automate any activities that are programmable and repeatable. They all can be used to bundle assets, but that’s not their core focus, and as a result they can be limited in some of the more complex needs for asset bundling—for example, identifying which of the generated assets won’t be used and discarding them from the final output. At its core, Mix is just a tool in your Webpack toolbox. The “Mix file” you’ll use to set your configurations is simply a Webpack configuration file which lives at the root of your project, named webpack.mix.js. However, the configuration you have to set there is a lot simpler than most Webpack configuration is out of the box, and you’ll have to do a lot less work to get most common asset compilation tasks working. Let’s look at a common example: running Sass to preprocess your CSS styles. In a normal Webpack environment, that might look a little bit like Example 6-1. Example 6-1. Compiling a Sass le in Webpack, before Mix var path = require(‘path’); var MiniCssExtractPlugin = require(“mini-css-extract-plugin”); module.exports = { entry: ‘./src/sass/app.scss’, module: { rules: [ { test: /\.s[ac]ss$/, use: [ MiniCssExtractPlugin.loader, “css-loader”, “sass-loader” ] } ] }, plugins: [ new MiniCssExtractPlugin({ path: path.resolve(__dirname, ‘./dist’), filename: ‘app.css’ }) ] } 160 | Chapter 6: Frontend Components Now, I’ve seen worse. There aren’t an unimaginable number of configuration proper‐ ties, and it’s relatively clear what’s going on. But this is the sort of code that you copy from project to project, not code you feel comfortable writing yourself or even modi‐ fying to any significant degree. Working like this can get confusing and repetitive. Let’s try that same task in Mix (Example 6-2). Example 6-2. Compiling a Sass le in Mix let mix = require(‘laravel-mix’); mix.sass(‘resources/sass/app.scss’, ‘public/css’); That’s it. And not only is it infinitely simpler, it also covers file watching, browser syncing, notifications, prescribed folder structures, autoprefixing, URL processing, and much more. Mix Folder Structure Much of Mix’s simplicity comes from the assumed directory structure. Why decide for every new application where the source and compiled assets will live? Just stick with Mix’s conventions, and you won’t have to think about it ever again. Every new Laravel app comes with a resources folder, which is where Mix will expect your frontend assets to live. Your Sass will live in resources/sass, or your Less in resources/less, or your source CSS in resources/css, and your JavaScript will live in resources/js. These will export to public/css and public/js. The Assets Subdirectory Prior to Laravel 5.7 In versions of Laravel prior to 5.7, the sass, less, and js directories were nested under the resources/assets directory instead of directly underneath the resources directory. Running Mix Since Mix runs on Webpack, you’ll need to set up a few tools before using it: 1. First, you’ll need Node.js installed. Visit the Node website to learn how to get it running. Once Node (and NPM with it) is installed once, you will not have to do this again for each project. Now you’re ready to install this project’s dependencies. Laravel Mix | 161 2. Open the project root in your terminal, and run npm install to install the required packages (Laravel ships with a Mix-ready package.json file to direct NPM). You’re now set up! You can run npm run dev to run Webpack/Mix once, npm run watch to listen for relevant file changes and run in response, or npm run prod to run Mix once with production settings (such as minifying the output). You can also run npm run watch-poll if npm run watch doesn’t work in your environment, or npm run hot for Hot Module Replacement (HMR; discussed in the next section). What Does Mix Provide? I’ve already mentioned that Mix can preprocess your CSS using Sass, Less, and/or PostCSS. It can also concatenate any sort of files, minify them, rename them, and copy them, and it can copy entire directories or individual files. Additionally, Mix can process all flavors of modern JavaScript and provide autopre‐ fixing, concatenation, and minification specifically as a part of the JavaScript build stack. It makes it easy to set up Browsersync, HMR, and versioning, and there are plug-ins available for many other common build scenarios. The Mix documentation covers all of these options and more, but we’ll discuss a few specific use cases in the following sections. Source maps If you’re not familiar with source maps, they work with any sort of preprocessor to teach your browser’s web inspector which files generated the compiled source you’re inspecting. By default, Mix will not generate source maps for your files. But you can enable them by chaining the sourceMaps() method after your Mix calls, as you can see in Example 6-3. Example 6-3. Enabling source maps in Mix let mix = require(‘laravel-mix’); mix.js(‘resources/js/app.js’, ‘public/js’) .sourceMaps(); Once you configure Mix this way, you’ll see the source maps appear as a .{le‐ name}.map file next to each generated file. Without source maps, if you use your browser’s development tools to inspect a partic‐ ular CSS rule or JavaScript action, you’ll just see a big mess of compiled code. With 162 | Chapter 6: Frontend Components source maps, your browser can pinpoint the exact line of the source file, whether it be Sass or JavaScript or whatever else, that generated the rule you’re inspecting. Pre- and post-processors We’ve already covered Sass and Less, but Mix can also handle Stylus (Example 6-4), and you can chain PostCSS onto any other style calls (Example 6-5). Example 6-4. Preprocessing CSS with Stylus mix.stylus(‘resources/stylus/app.styl’, ‘public/css’); Example 6-5. Post-processing CSS with PostCSS mix.sass(‘resources/sass/app.scss’, ‘public/css’) .options({ postCss: [ require(‘postcss-css-variables’)() ] }); Preprocessorless CSS If you don’t want to deal with a preprocessor, there’s a command for that—it will grab all of your CSS files, concatenate them, and output them to the public/css directory, just as if they had been run through a preprocessor. There are a few options, which you can see in Example 6-6. Example 6-6. Combining stylesheets with Mix // Combines all files from resources/css mix.styles(‘resources/css’, ‘public/css/all.css’); // Combines files from resources/css mix.styles([ ‘resources/css/normalize.css’, ‘resources/css/app.css’ ], ‘public/css/all.css’); Concatenating JavaScript The options available for working with normal JavaScript files are very similar to those available for normal CSS files. Take a look at Example 6-7. Example 6-7. Combining JavaScript les with Mix let mix = require(‘laravel-mix’); Laravel Mix | 163 // Combines all files from resources/js mix.scripts(‘resources/js’, ‘public/js/all.js’); // Combines files from resources/js mix.scripts([ ‘resources/js/normalize.js’, ‘resources/js/app.js’ ], ‘public/js/all.js’); Processing JavaScript If you want to process your JavaScript—for example, to compile your ES6 code into plain JavaScript—Mix makes it easy to use Webpack for this purpose (see Example 6-8). Example 6-8. Processing JavaScript les in Mix with Webpack let mix = require(‘laravel-mix’); mix.js(‘resources/js/app.js’, ‘public/js’); These scripts look for the provided filename in resources/js and output to public/js/ app.js. You can use more complicated aspects of Webpack’s feature set by creating a webpack.cong.js file in your project root. Copying les or directories To move either a single file or an entire directory, use the copy() method or the copy Directory() method: mix.copy(‘node_modules/pkgname/dist/style.css’, ‘public/css/pkgname.css’); mix.copyDirectory(‘source/images’, ‘public/images’); Versioning Most of the tips from Steve Souders’s Even Faster Web Sites (O’Reilly) have made their way into our everyday development practices. We move scripts to the footer, reduce the number of HTTP requests, and more, often without even realizing where those ideas originated. One of Steve’s tips is still very rarely implemented, though, and that is setting a very long cache life on assets (scripts, styles, and images). Doing this means there will be fewer requests to your server to get the latest version of your assets. But it also means that users are extremely likely to have a cached version of your assets, which will make things get outdated, and therefore break, quickly. 164 | Chapter 6: Frontend Components The solution to this is versioning. Append a unique hash to each asset’s filename every time you run your build script, and then that unique file will be cached indefinitely— or at least until the next build. What’s the problem? Well, first you need to get the unique hashes generated and appended to your filenames. But you also will need to update your views on every build to reference the new filenames. As you can probably guess, Mix handles that for you, and it’s incredibly simple. There are two components: the versioning task in Mix, and the mix() PHP helper. First, you can version your assets by running mix.version() like in Example 6-9. Example 6-9. mix.version let mix = require(‘laravel-mix’); mix.sass(‘resources/sass/app.scss’, ‘public/css’) .version(); The version of the file that’s generated is no different—it’s just named app.css and lives in public/css. Versioning Assets Using Query Parameters The way versioning is handled in Laravel is a little different from traditional versioning, in that the versioning is appended with a query parameter instead of by modifying filenames. It still func‐ tions the same way, because browsers read it as a “new” file, but it handles a few edge cases with caches and load balancers. Next, use the PHP mix() helper in your views to refer to that file like in Example 6-10. Example 6-10. Using the mix() helper in views // Will output something like: Laravel Mix | 165 How Does Mix Versioning Work Behind the Scenes? Mix generates a file named public/mix-manifest.json. This stores the information the mix() helper needs to find the generated file. Here’s what a sample mix-manifest.json looks like: { “/css/app.css”: “/css/app.css?id=4151cf6261b95f07227e” } Vue and React Mix can handle building both Vue (with single-file components) and React compo‐ nents. Mix’s default js() call handles Vue, and you can replace it with a react() call if you want to build React components: mix.react(‘resources/js/app.js’, ‘public/js’); If you take a look at the default Laravel sample app.js and the components it imports (Example 6-11), you’ll see that you don’t have to do anything special to work with Vue components. A simple mix.js() call makes this possible in your app.js. Example 6-11. App.js congured to work with Vue window.Vue = require(‘vue’); Vue.component(‘example-component’, require(‘./components/ExampleComponent.vue’)); const app = new Vue({ el: ‘#app’ }); And if you switch to react(), this is all you need to run in your file for your first component: require(‘./components/Example’); Both presets also bring in Axios, Lodash, and Popper.js, so you don’t have to spend any time getting your Vue or React ecosystems set up. Hot Module Replacement When you’re writing single components with Vue or React, you’re likely used to either refreshing the page every time your build tool recompiles your components or, if you’re using something like Mix, relying on Browsersync to reload it for you. 166 | Chapter 6: Frontend Components That’s great, but if you’re working with single-page apps (SPAs), that means you’re booted back to the beginning of the app; that refresh wipes any state you had built up as you navigated through the app. Hot Module Replacement (HMR, sometimes called hot reloading) solves this prob‐ lem. It’s not always easy to set up, but Mix comes with it enabled out of the box. HMR works essentially as if you’d taught Browsersync to not reload the entire file that was recompiled, but instead to just reload the bits of code you changed. That means you can get the updated code injected into your browser, but still retain the state you had built up as you got your SPA into just the right spot for testing. To use HMR, you’ll want to run npm run hot instead of npm run watch. In order for it to work correctly, all of your references have to be pulling the right ver‐ sions of your JavaScript files. Essentially, Mix is booting up a small Node server at localhost:8080, so if your tag points to a different version of the script, HMR won’t work. The easiest way to achieve this is to just use the mix() helper to reference your scripts. This helper will handle prepending either localhost:8080 if in HMR mode or your domain if you’re in a normal development mode. Here’s what it looks like inline: If you develop your applications on an HTTPS connection—for example, if you run valet secure—all your assets must also be served via an HTTPS connection. This is a little bit trickier, so it’s best to consult the HMR docs. Vendor extraction The most common frontend bundling pattern, which Mix also encourages, ends up generating a single CSS file and a single JavaScript file that encompasses both the appspecific code for your project and the code for all its dependencies. However, this means that vendor file updates require the entire file to be rebuilt and recached, which might introduce an undesirable load time. Mix makes it easy to extract all of the JavaScript from your app’s dependencies into a separate vendor.js file. Simply supply a list of the vendor’s library names to the extract() method, chained after your js() call. Take a look at Example 6-12 to see how it looks. Laravel Mix | 167 Example 6-12. Extracting a vendor library into a separate le mix.js(‘resources/js/app.js’, ‘public/js’) .extract([‘vue’]) This outputs your existing app.js and then two new files: manifest.js, which gives instructions to your browser about how to load the dependencies and app code, and vendor.js, which contains the vendor-specific code. It’s important to load these files in the correct order in your frontend code—first manifest.js, then vendor.js, and finally app.js: Extracting All Dependencies Using extract() in Mix 4.0+ If your project is using Laravel Mix 4.0 or greater, you can call the extract() method with no arguments. This will extract the entire dependency list for your application. Environment variables in Mix As Example 6-13 shows, if you prefix an environment variable (in your .env file) with MIX_, it will become available in your Mix-compiled files with the naming convention process.env.ENV_VAR_NAME. Example 6-13. Using .env variables in Mix-compiled JavaScript # In your .env file MIX_BUGSNAG_KEY=lj12389g08bq1234 MIX_APP_NAME=”Your Best App Now” // In Mix-compiled files process.env.MIX_BUGSNAG_KEY // For example, this code: console.log(“Welcome to ” + process.env.MIX_APP_NAME); // Will compile down to this: console.log(“Welcome to ” + “Your Best App Now”); You can also access those variables in your Webpack configuration files using Node’s dotenv package, as shown in Example 6-14. 168 | Chapter 6: Frontend Components Example 6-14. Using .env variables in Webpack conguration les // webpack.mix.js let mix = require(‘laravel-mix’); require(‘dotenv’).config(); let isProduction = process.env.MIX_ENV === “production”; Frontend Presets and Auth Scaffolding As a full-stack framework, Laravel has more connections to and opinions about frontend tooling than your average backend framework. Out of the box it provides an entire build system, which we’ve already covered, but it also builds and has compo‐ nents for Vue and includes Bootstrap, Axios, and Lodash. Frontend Presets You can get a sense of the frontend tools that come along with each new Laravel install by taking a look at package.json, webpack.mix.js (or gulple.js in older versions of Laravel), and the views, JavaScript files, and CSS files in the resources directory. This default set of components and files is called the Vue preset, and every new Lara‐ vel project comes stocked with it. But what if you’d rather work in React? What if you want Bootstrap but not all that JavaScript? And what if you want to just rip it all out? Enter frontend presets, intro‐ duced in Laravel 5.5: these are pre-baked scripts that modify or remove part or all of the Vue- and Bootstrap-loaded default presets. You can use the presets that are pro‐ vided out of the box, or you can pull in third-party presets from GitHub. To use a built-in preset, simply run php artisan present preset_name: php artisan preset react php artisan preset bootstrap php artisan preset none There’s also a vue preset, which is what each new application has applied on a fresh install. Third-party frontend presets If you’re interested in creating your own preset, or using one created by another com‐ munity member, that’s also possible with the frontend preset system. There’s a GitHub organization designed to make it easy to find great third-party frontend presets, and they’re easy to install. For most, the steps are as follows: 1. Install the package (e.g., composer require laravel-frontend-presets/tail windcss). Frontend Presets and Auth Scaffolding | 169 2. Install the preset (e.g., php artisan preset tailwindcss). 3. Just like with the built-in presets, run npm install and npm run dev. If you want to create a preset of your own, the same organization has a skeleton repository you can fork to make it easier. Auth Scaffolding Although they’re technically not a part of the frontend presets, Laravel has a series of routes and views called the auth scaffold that are, essentially, frontend presets. If you run php artisan make:auth, you’ll get a login page, a signup page, a new master template for the “app” view of your app, routes to serve these pages, and more. Take a look at Chapter 9 to learn more. Pagination For something that is so common across web applications, pagination still can be wildly complicated to implement. Thankfully, Laravel has a built-in concept of pagi‐ nation, and it’s also hooked into Eloquent results and the router by default. Paginating Database Results The most common place you’ll see pagination is when you are displaying the results of a database query and there are too many results for a single page. Eloquent and the query builder both read the page query parameter from the current page request and use it to provide a paginate() method on any result sets; the single parameter you should pass paginate() is how many results you want per page. Take a look at Example 6-15 to see how this works. Example 6-15. Paginating a query builder response // PostsController public function index() { return view(‘posts.index’, [‘posts’ => DB::table(‘posts’)->paginate(20)]); } Example 6-15 specifies that this route should return 20 posts per page, and will define which “page” of results the current user is on based on the URL’s page query parame‐ ter, if it has one. Eloquent models all have the same paginate() method. When you display the results in your view, your collection will now have a links() method on it (or render() for Laravel 5.1) that will output the pagination controls, 170 | Chapter 6: Frontend Components with class names from the Bootstrap component library assigned to them by default (see Example 6-16). Example 6-16. Rendering pagination links in a template // posts/index.blade.php{{ $post->title }} |
The email field is required.
‘], ‘:message
‘ ); If you’re working with named error bags, you can pass the error bag name as the third parameter. $response->assertCookie($name, $value = null) Asserts that the response contains a cookie with name $name, and optionally checks that its value is $value: $response = $this->post(‘settings’, [‘dismiss-warning’]); $response->assertCookie(‘warning-dismiss’, true); $response->assertCookieExpired($name) Asserts that the response contains a cookie with name $name and that it is expired: $response->assertCookieExpired(‘warning-dismiss’); $response->assertCookieNotExpired($name) Asserts that the response contains a cookie with name $name and that it is not expired: $response->assertCookieNotExpired(‘warning-dismiss’); $response->assertRedirect($uri) Asserts that the requested route returns a redirect to the given URI: $response = $this->post(route(‘packages.store’), [ ’email’ => ‘invalid’ ]); $response->assertRedirect(route(‘packages.create’)); For each of these assertions, you can assume that there are many related assertions I haven’t listed here. For example, in addition to assertSessionHasErrors() there are also assertSessionHasNoErrors() and assertSessionHasErrorsIn() assertions; as well as assertJson(), there are also assertJsonCount(), assertJsonFragment(), assertJsonMissing(), assertJsonMissingExact(), assertJsonStructure(), and assertJsonValidationErrors() assertions. Again, take a look at the docs and make yourself familiar with the whole list. 308 | Chapter 12: Testing Authenticating Responses One piece of your application it’s common to test with application tests is authentica‐ tion and authorization. Most of the time your needs will be met with the actingAs() chainable method, which takes a user (or other Authenticatable object, depending on how your system is set up), as you can see in Example 12-10. Example 12-10. Basic auth in testing public function test_guests_cant_view_dashboard() { $user = factory(User::class)->states(‘guest’)->create(); $response = $this->actingAs($user)->get(‘dashboard’); $response->assertStatus(401); // Unauthorized } public function test_members_can_view_dashboard() { $user = factory(User::class)->states(‘member’)->create(); $response = $this->actingAs($user)->get(‘dashboard’); $response->assertOk(); } public function test_members_and_guests_cant_view_statistics() { $guest = factory(User::class)->states(‘guest’)->create(); $response = $this->actingAs($guest)->get(‘statistics’); $response->assertStatus(401); // Unauthorized $member = factory(User::class)->states(‘member’)->create(); $response = $this->actingAs($member)->get(‘statistics’); $response->assertStatus(401); // Unauthorized } public function test_admins_can_view_statistics() { $user = factory(User::class)->states(‘admin’)->create(); $response = $this->actingAs($user)->get(‘statistics’); $response->assertOk(); } Using Factory States for Authorization It’s common to use model factories (discussed in “Model Factories” on page 99) in testing, and model factory states make tasks like cre‐ ating users with different access levels simple. HTTP Tests | 309 A Few Other Customizations to Your HTTP Tests If you’d like to set session variables on your requests, you can also chain withSession(): $response = $this->withSession([ ‘alert-dismissed’ => true, ])->get(‘dashboard’); If you’d prefer to set your request headers fluently, you can chain withHeaders(): $response = $this->withHeaders([ ‘X-THE-ANSWER’ => ’42’, ])->get(‘the-restaurant-at-the-end-of-the-universe’); Handling Exceptions in Application Tests Usually, an exception that’s thrown inside your application when you’re making HTTP calls will be captured by Laravel’s exception handler and processed as it would be in normal application. So, the test and route in Example 12-11 would still pass, since the exception would never bubble up the whole way to our test. Example 12-11. An exception that will be captured by Laravel’s exception handler and result in a passing test // routes/web.php Route::get(‘has-exceptions’, function () { throw new Exception(‘Stop!’); }); // tests/Feature/ExceptionsTest.php public function test_exception_in_route() { $this->get(‘/has-exceptions’); $this->assertTrue(true); } In a lot of cases, this might make sense; maybe you’re expecting a validation exception and you want it to be caught like it would normally be by the framework. But if you want to temporarily disable the exception handler, that’s an option; just run $this->withoutExceptionHandling(), as shown in Example 12-12. Example 12-12. Temporarily disabling exception handling in a single test // tests/Feature/ExceptionsTest.php public function test_exception_in_route() { // Now throws an error 310 | Chapter 12: Testing $this->withoutExceptionHandling(); $this->get(‘/has-exceptions’); $this->assertTrue(true); } And if for some reason you need to turn it back on (maybe you turned it off in setUp() but want it back on for just one test), you can run $this->withException Handling(). Database Tests Often, the effect we want to test for after our tests have run is in the database. Imag‐ ine you want to test that the “create package” page works correctly. What’s the best way? Make an HTTP call to the “store package” endpoint and then assert that that package exists in the database. It’s easier and safer than inspecting the resulting “list packages” page. We have two primary assertions for the database: $this->assertDatabaseHas() and $this->assertDatabaseMissing(). For both, pass the table name as the first param‐ eter, the data you’re looking for as the second, and, optionally, the specific database connection you want to test as the third. Take a look at Example 12-13 to see how you might use them. Example 12-13. Sample database tests public function test_create_package_page_stores_package() { $this->post(route(‘packages.store’), [ ‘name’ => ‘Package-a-tron’, ]); $this->assertDatabaseHas(‘packages’, [‘name’ => ‘Package-a-tron’]); } As you can see, the second (data) parameter of assertDatabaseHas() is structured like a SQL WHERE statement—you pass a key and a value (or multiple keys and values), and then Laravel looks for any records in the specified database table that match your key(s) and value(s). As always, assertDatabaseMissing() is the inverse. Database Tests | 311 Using Model Factories in Tests Model factories are amazing tools that make it easy to seed randomized, wellstructured database data for testing (or other purposes). You’ve already seen them in use in several examples in this chapter. We’ve already covered them in depth, so check out “Model Factories” on page 99 to learn more. Seeding in Tests If you use seeds in your application, you can run the equivalent of php artisan db:seed by running $this->seed() in your test. You can also pass a seeder class name to just seed that one class: $this->seed(); // Seeds all $this->seed(UserSeeder::class); // Seeds users Testing Other Laravel Systems When testing Laravel systems, you’ll often want to pause their true function for the duration of the testing and instead write tests against what has happened to those sys‐ tems. You can do this by “faking” different facades, such as Event, Mail, and Notifi cation. We’ll talk more about what fakes are in “Mocking” on page 318, but first, let’s look at some examples. All of the following features in Laravel have their own set of assertions you can make after faking them, but you can also just choose to fake them to restrict their effects. Event Fakes Let’s use event fakes as our first example of how Laravel makes it possible to mock its internal systems. There are likely going to be times when you want to fake events just for the sake of suppressing their actions. For example, suppose your app pushes noti‐ fications to Slack every time a new user signs up. You have a “user signed up” event that’s dispatched when this happens, and it has a listener that notifies a Slack channel that a user has signed up. You don’t want those notifications to go to Slack every time you run your tests, but you might want to assert that the event was sent, or the lis‐ tener was triggered, or something else. This is one reason for faking certain aspects of Laravel in our tests: to pause the default behavior and instead make assertions against the system we’re testing. Let’s take a look at how to suppress these events by calling the fake() method on Illuminate\Support\Facades\Event, as shown in Example 12-14. 312 | Chapter 12: Testing Example 12-14. Suppressing events without adding assertions public function test_controller_does_some_thing() { Event::fake(); // Call controller and assert it does whatever you want without // worrying about it pinging Slack } Once we’ve run the fake() method, we can also call special assertions on the Event facade: namely, assertDispatched() and assertNotDispatched(). Take a look at Example 12-15 to see them in use. Example 12-15. Making assertions against events public function test_signing_up_users_notifies_slack() { Event::fake(); // Sign user up Event::assertDispatched(UserJoined::class, function ($event) use ($user) { return $event->user->id === $user->id; }); // Or sign multiple users up and assert it was dispatched twice Event::assertDispatched(UserJoined::class, 2); // Or sign up with validation failures and assert it wasn’t dispatched Event::assertNotDispatched(UserJoined::class); } Note that the (optional) closure we’re passing to assertDispatched() makes it so we’re not just asserting that the event was dispatched, but also that the dispatched event contains certain data. Event::fake() Disables Eloquent Model Events Event::fake() also disables Eloquent model events. So if you have any important code, for example, in a model’s creating event, make sure to create your models (through your factories or how‐ ever else) before calling Event::fake(). Testing Other Laravel Systems | 313 Bus and Queue Fakes The Bus facade, which represents how Laravel dispatches jobs, works just like Event. You can run fake() on it to disable the impact of your jobs, and after faking it you can run assertDispatched() or assertNotDispatched(). The Queue facade represents how Laravel dispatches jobs when they’re pushed up to queues. Its available methods are assertedPushed(), assertPushedOn(), and assert NotPushed(). Take a look at Example 12-16 to see how to use both. Example 12-16. Faking jobs and queued jobs public function test_popularity_is_calculated() { Bus::fake(); // Synchronize package data… // Assert a job was dispatched Bus::assertDispatched( CalculatePopularity::class, function ($job) use ($package) { return $job->package->id === $package->id; } ); // Assert a job was not dispatched Bus::assertNotDispatched(DestroyPopularityMaybe::class); } public function test_popularity_calculation_is_queued() { Queue::fake(); // Synchronize package data… // Assert a job was pushed to any queue Queue::assertPushed(CalculatePopularity::class, function ($job) use ($package) { return $job->package->id === $package->id; }); // Assert a job was pushed to a given queue named “popularity” Queue::assertPushedOn(‘popularity’, CalculatePopularity::class); // Assert a job was pushed twice Queue::assertPushed(CalculatePopularity::class, 2); // Assert a job was not pushed 314 | Chapter 12: Testing Queue::assertNotPushed(DestroyPopularityMaybe::class); } Mail Fakes The Mail facade, when faked, offers four methods: assertSent(), assertNotSent(), assertQueued(), and assertNotQueued(). Use the Queued methods when your mail is queued and the Sent methods when it’s not. Just like with assertDispatched(), the first parameter will be the name of the maila‐ ble and the second parameter can be empty, the number of times the mailable has been sent, or a closure testing that the mailable has the right data in it. Take a look at Example 12-17 to see a few of these methods in action. Example 12-17. Making assertions against mail public function test_package_authors_receive_launch_emails() { Mail::fake(); // Make a package public for the first time… // Assert a message was sent to a given email address Mail::assertSent(PackageLaunched::class, function ($mail) use ($package) { return $mail->package->id === $package->id; }); // Assert a message was sent to given email addresses Mail::assertSent(PackageLaunched::class, function ($mail) use ($package) { return $mail->hasTo($package->author->email) && $mail->hasCc($package->collaborators) && $mail->hasBcc(‘admin@novapackages.com’); }); // Or, launch two packages… // Assert a mailable was sent twice Mail::assertSent(PackageLaunched::class, 2); // Assert a mailable was not sent Mail::assertNotSent(PackageLaunchFailed::class); } All of the messages checking for recipients (hasTo(), hasCc(), and hasBcc()) can take either a single email address or an array or collection of addresses. Testing Other Laravel Systems | 315 Notication Fakes The Notification facade, when faked, offers two methods: assertSentTo() and assertNothingSent(). Unlike with the Mail facade, you’re not going to test who the notification was sent to manually in a closure. Rather, the assertion itself requires the first parameter be either a single notifiable object or an array or collection of them. Only after you’ve passed in the desired notification target can you test anything about the notification itself. The second parameter is the class name for the notification, and the (optional) third parameter can be a closure defining more expectations about the notification. Take a look at Example 12-18 to learn more. Example 12-18. Notication fakes public function test_users_are_notified_of_new_package_ratings() { Notification::fake(); // Perform package rating… // Assert author was notified Notification::assertSentTo( $package->author, PackageRatingReceived::class, function ($notification, $channels) use ($package) { return $notification->package->id === $package->id; } ); // Assert a notification was sent to the given users Notification::assertSentTo( [$package->collaborators], PackageRatingReceived::class ); // Or, perform a duplicate package rating… // Assert a notification was not sent Notification::assertNotSentTo( [$package->author], PackageRatingReceived::class ); } You may also find yourself wanting to assert that your channel selection is working— that notifications are sent via the right channels. You can test that as well, as you can see in Example 12-19. 316 | Chapter 12: Testing Example 12-19. Testing notication channels public function test_users_are_notified_by_their_preferred_channel() { Notification::fake(); $user = factory(User::class)->create([‘slack_preferred’ => true]); // Perform package rating… // Assert author was notified via Slack Notification::assertSentTo( $user, PackageRatingReceived::class, function ($notification, $channels) use ($package) { return $notification->package->id === $package->id && in_array(‘slack’, $channels); } ); Storage Fakes Testing files can be extraordinarily complex. Many traditional methods require you to actually move files around in your test directories, and formatting the form input and output can be very complicated. Thankfully, if you use Laravel’s Storage facade, it’s infinitely simpler to test file uploads and other storage-related items. Example 12-20 demonstrates. Example 12-20. Testing storage and le uploads with storage fakes public function test_package_screenshot_upload() { Storage::fake(‘screenshots’); // Upload a fake image $response = $this->postJson(‘screenshots’, [ ‘screenshot’ => UploadedFile::fake()->image(‘screenshot.jpg’), ]); // Assert the file was stored Storage::disk(‘screenshots’)->assertExists(‘screenshot.jpg’); // Or, assert a file does not exist Storage::disk(‘screenshots’)->assertMissing(‘missing.jpg’); } Testing Other Laravel Systems | 317 Mocking Mocks (and their brethren, spies and stubs and dummies and fakes and any number of other tools) are common in testing. We saw some examples of fakes in the previous section. I won’t go into too much detail here, but it’s unlikely you can thoroughly test an application of any size without mocking at least one thing or another. So, lets take a quick look at mocking in Laravel and how to use Mockery, the mocking library. A Quick Introduction to Mocking Essentially, mocks and other similar tools make it possible to create an object that in some way mimics a real class, but for testing purposes isn’t the real class. Sometimes this is done because the real class is too difficult to instantiate just to inject it into a test, or maybe because the real class communicates with an external service. As you can probably tell from the examples that follow, Laravel encourages working with the real application as much as possible—which means avoiding too great of a dependence on mocks. But they have their place, which is why Laravel includes Mockery, a mocking library, out of the box, and is why many of its core services offer faking utilities. A Quick Introduction to Mockery Mockery allows you to quickly and easily create mocks from any PHP class in your application. Imagine you have a class that depends on a Slack client, but you don’t want the calls to actually go out to Slack. Mockery makes it simple to create a fake Slack client to use in your tests, like you can see in Example 12-21. Example 12-21. Using Mockery in Laravel // app/SlackClient.php class SlackClient { // … public function send($message, $channel) { // Actually sends a message to Slack } } // app/Notifier.php class Notifier { private $slack; 318 | Chapter 12: Testing public function __construct(SlackClient $slack) { $this->slack = $slack; } public function notifyAdmins($message) { $this->slack->send($message, ‘admins’); } } // tests/Unit/NotifierTest.php public function test_notifier_notifies_admins() { $slackMock = Mockery::mock(SlackClient::class)->shouldIgnoreMissing(); $notifier = new Notifier($slackMock); $notifier->notifyAdmins(‘Test message’); } There are a lot of elements at work here, but if you look at them one by one, they make sense. We have a class named Notifier that we’re testing. It has a dependency named SlackClient that does something that we don’t want it to do when we’re run‐ ning our tests: it sends actual Slack notifications. So we’re going to mock it. We use Mockery to get a mock of our SlackClient class. If we don’t care about what happens to that class—if it should simply exist to keep our tests from throwing errors —we can just use shouldIgnoreMissing(): $slackMock = Mockery::mock(SlackClient::class)-shouldIgnoreMissing(); No matter what Notifier calls on $slackMock, it’ll just accept it and return null. But take a look at test_notifier_notifies_admins(). At this point, it doesn’t actually test anything. We could just keep shouldIgnoreMissing() and then write some assertions below it. That’s usually what we do with shouldIgnoreMissing(), which makes this object a “fake” or a “stub.” But what if we want to actually assert that a call was made to the send() method of SlackClient? That’s when we drop shouldIgnoreMissing() and reach for the other should* methods (Example 12-22). Example 12-22. Using the shouldReceive() method on a Mockery mock public function test_notifier_notifies_admins() { $slackMock = Mockery::mock(SlackClient::class); Mocking | 319 $slackMock->shouldReceive(‘send’)->once(); $notifier = new Notifier($slackMock); $notifier->notifyAdmins(‘Test message’); } shouldReceive(‘send’)->once() is the same as saying “assert that $slackMock will have its send() method called once and only once.” So, we’re now asserting that Notifier, when we call notifyAdmins(), makes a single call to the send() method on SlackClient. We could also use something like shouldReceive(‘send’)->times(3) or shouldReceive(‘send’)->never(). We can define what parameter we expect to be passed along with that send() call using with(), and we can define what to return with andReturn(): $slackMock->shouldReceive(‘send’)->with(‘Hello, world!’)->andReturn(true); What if we wanted to use the IoC container to resolve our instance of the Notifier? This might be useful if Notifier had several other dependencies that we didn’t need to mock. We can do that! We just use the instance() method on the container, as in Example 12-23, to tell Laravel to provide an instance of our mock to any classes that request it (which, in this example, will be Notifier). Example 12-23. Binding a Mockery instance to the container public function test_notifier_notifies_admins() { $slackMock = Mockery::mock(SlackClient::class); $slackMock->shouldReceive(‘send’)->once(); app()->instance(SlackClient::class, $slackMock); $notifier = app(Notifier::class); $notifier->notifyAdmins(‘Test message’); } In Laravel 5.8+, there’s also a convenient shortcut to creating and binding a Mockery instance to the container: Example 12-24. Binding Mockery instances to the container more easily in Laravel 5.8+ $this->mock(SlackClient::class, function ($mock) { $mock->shouldReceive(‘send’)->once(); }); 320 | Chapter 12: Testing There’s a lot more you can do with Mockery: you can use spies, and partial spies, and much more. Going deeper into how to use Mockery is out of the scope of this book, but I encourage you to learn more about the library and how it works by reading the Mockery docs. Faking Other Facades There’s one other clever thing you can do with Mockery: you can use Mockery meth‐ ods (e.g., shouldReceive()) on any facades in your app. Imagine we have a controller method that uses a facade that’s not one of the fakeable systems we’ve already covered; we want to test that controller method and assert that a certain facade call was made. Thankfully, it’s simple: we can run our Mockery-style methods on the facade, as you can see in Example 12-25. Example 12-25. Mocking a facade // PeopleController public function index() { return Cache::remember(‘people’, function () { return Person::all(); }); } // PeopleTest public function test_all_people_route_should_be_cached() { $person = factory(Person::class)->create(); Cache::shouldReceive(‘remember’) ->once() ->andReturn(collect([$person])); $this->get(‘people’)->assertJsonFragment([‘name’ => $person->name]); } As you can see, you can use methods like shouldReceive() on the facades, just like you do on a Mockery object. You can also use your facades as spies, which means you can set your assertions at the end and use shouldHaveReceived() instead of shouldReceive(). Example 12-26 illustrates this. Mocking | 321 Example 12-26. Facade spies public function test_package_should_be_cached_after_visit() { Cache::spy(); $package = factory(Package::class)->create(); $this->get(route(‘packages.show’, [$package->id])); Cache::shouldHaveReceived(‘put’) ->once() ->with(‘packages.’ . $package->id, $package->toArray()); } Testing Artisan Commands We’ve covered a lot in this chapter, but we’re almost done! We have just two more pieces of Laravel’s testing arsenal to cover: Artisan and the browser. If you’re working in Laravel prior to 5.7, the best way to test Artisan commands is to call them with $this->artisan($commandName, $parameters) and then test their impact, like in Example 12-27. Example 12-27. Simple Artisan tests public function test_promote_console_command_promotes_user() { $user = factory(User::class)->create(); $this->artisan(‘user:promote’, [‘userId’ => $user->id]); $this->assertTrue($user->isPromoted()); } You can also make assertions against the response code you get from Artisan, as you can see in Example 12-28. Example 12-28. Manually asserting Artisan exit codes $code = $this->artisan(‘do:thing’, [‘–flagOfSomeSort’ => true]); $this->assertEquals(0, $code); // 0 means “no errors were returned” Asserting Against Artisan Command Syntax If you’re working with Laravel 5.7 and later, you can also chain three new methods onto your $this->artisan() call: expectsQuestion(), expectsOutput(), and assertExitCode(). The expects* methods will work on any of the interactive 322 | Chapter 12: Testing prompts, including confirm(), and anticipate(), and the assertExitCode() method is a shortcut to what we saw in Example 12-28. Take a look at Example 12-29 to see how it works. Example 12-29. Basic Artisan “expects” tests // routes/console.php Artisan::command(‘make:post {–expanded}’, function () { $title = $this->ask(‘What is the post title?’); $this->comment(‘Creating at ‘ . str_slug($title) . ‘.md’); $category = $this->choice(‘What category?’, [‘technology’, ‘construction’], 0); // Create post here $this->comment(‘Post created’); }); // Test file public function test_make_post_console_commands_performs_as_expected() { $this->artisan(‘make:post’, [‘–expanded’ => true]) ->expectsQuestion(‘What is the post title?’, ‘My Best Post Now’) ->expectsOutput(‘Creating at my-best-post-now.md’) ->expectsQuestion(‘What category?’, ‘construction’) ->expectsOutput(‘Post created’) ->assertExitCode(0); } As you can see, the first parameter of expectsQuestion() is the text we’re expecting to see from the question, and the second parameter is the text we’re answering with. expectsOutput() just tests that the passed string is returned. Browser Tests We’ve made it to browser tests! These allow you to actually interact with the DOM of your pages: in browser tests you can click buttons, fill out and submit forms, and, with Dusk, even interact with JavaScript. Laravel actually has two separate browser testing tools: BrowserKit Testing and Dusk. Only Dusk is actively maintained; BrowserKit Testing seems to have become a bit of a second-class citizen, but it’s still available on GitHub and still works at the time of this writing. Browser Tests | 323 Choosing a Tool For browser testing, I suggest you use the core application testing tools whenever possible (those we’ve covered up to this point). If your app is not JavaScript-based and you need to test actual DOM manipulation or form UI elements, use BrowserKit. If you’re developing a JavaScript-heavy app, you’ll likely want to use Dusk, which we’ll cover next. However, there will also be many instances where you’ll want to use a JavaScriptbased test stack (which is out of scope for this book) based on something like Jest and vue-test-utils. This toolset can be very useful for Vue component testing, and Jest’s snapshot functionality simplifies the process of keeping API and frontend test data in sync. To learn more, check out Caleb Porzio’s “Getting Started” blog post and Saman‐ tha Geitz’s 2018 Laracon talk. If you’re working with a JavaScript framework other than Vue, there are no currently preferred frontend testing solutions in the Laravel world. However, the broad React world seems to have settled on Jest and Enzyme. A Quick Introduction to BrowserKit Testing The BrowserKit Testing package is the code from Laravel pre-5.4 application testing pulled out into a separate package. BrowserKit is a component that parses the DOM and allows you to “select” DOM elements and interact with them. This is great for simple page interactions like clicking links and filling out forms, but it doesn’t work for JavaScript. BrowserKit Testing hasn’t been abandoned, but it’s also not ever mentioned in the docs, and it definitely has the feel of deprecated legacy code. For this reason, and because of the robustness of the built-in application testing suite, I’m going to skip covering it here. However, the first edition of this book covered it extensively, so if you are interested in working with Browserkit Testing, check out the free PDF of the first edition’s testing chapter. Testing with Dusk Dusk is a Laravel tool (installable as a Composer package) that makes it easy to write Selenium-style directions for a ChromeDriver-based browser to interact with your app. Unlike most other Selenium-based tools, Dusk’s API is simple and it’s easy to write code to interact with it by hand. Take a look: $this->browse(function ($browser) { $browser->visit(‘/register’) ->type(’email’, ‘test@example.com’) ->type(‘password’, ‘secret’) 324 | Chapter 12: Testing ->press(‘Sign Up’) ->assertPathIs(‘/dashboard’); }); With Dusk, there’s an actual browser spinning up your entire application and inter‐ acting with it. That means you can have complex interactions with your JavaScript and get screenshots of failure states—but it also means everything’s a bit slower and it’s more prone to failure than Laravel’s base application testing suite. Personally, I’ve found that Dusk is most useful as a regression testing suite, and it works better than something like Selenium. Rather than using it for any sort of testdriven development, I use it to assert that the user experience hasn’t broken (“regressed”) as the app continues to develop. Think of this more like writing tests about your user interface after the interface is built. The Dusk docs are robust, so I’m not going to go into great depth here, but I want to show you the basics of working with Dusk. Installing Dusk To install Dusk, run these two commands: composer require –dev laravel/dusk php artisan dusk:install Then edit your .env file to set your APP_URL variable to the same URL you use to view your site in your local browser; something like http://mysite.test. To run your Dusk tests, just run php artisan dusk. You can pass in all the same parameters you’re used to from PHPUnit (for example, php artisan dusk — filter=my_best_test). Writing Dusk tests To generate a new Dusk test, use a command like the following: php artisan dusk:make RatingTest This test will be placed in tests/Browser/RatingTest.php. Customizing Dusk Environment Variables You can customize the environment variables for Dusk by creating a new file named .env.dusk.local (and you can replace .local if you’re working in a different environment, like “staging”). To write your Dusk tests, imagine that you’re directing one or more web browsers to visit your application and take certain actions. That’s what the syntax will look like, as you can see in Example 12-30. Browser Tests | 325 Example 12-30. A simple Dusk test public function testBasicExample() { $user = factory(User::class)->create(); $this->browse(function ($browser) use ($user) { $browser->visit(‘login’) ->type(’email’, $user->email) ->type(‘password’, ‘secret’) ->press(‘Login’) ->assertPathIs(‘/home’); }); } $this->browse() creates a browser, which you pass into a closure; then, within the closure you instruct the browser which actions to take. It’s important to note that—unlike Laravel’s other application testing tools, which mimic the behavior of your forms—Dusk is actually spinning up a browser, sending events to the browser to type those words, and then sending an event to the browser to press that button. This is a real browser and Dusk is fully driving it. You can also “ask” for more than one browser by adding parameters to the closure, which allows you to test how multiple users might interact with the website (for example, with a chat system). Take a look at Example 12-31, from the docs. Example 12-31. Multiple Dusk browsers $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’); }); There’s a huge suite of actions and assertions available that we won’t cover here (check the docs), but let’s look at a few of the other tools Dusk provides. 326 | Chapter 12: Testing Authentication and databases As you can see in Example 12-31, the syntax for authentication is a little different from the rest of the Laravel application testing: $browser->loginAs($user). Avoid the RefreshDatabase Trait with Dusk Don’t use the RefreshDatabase trait with Dusk! Use the Database Migrations trait instead; transactions, which RefreshDatabase uses, don’t last across requests. Interactions with the page If you’ve ever written jQuery, interacting with the page using Dusk will come natu‐ rally. Take a look at Example 12-32 to see the common patterns for selecting items with Dusk. Example 12-32. Selecting items with Dusk // Dusk tests // Option 1: jQuery-style syntax $browser->click(‘.search button’); $browser->click(‘#search-button’); // Option 2: dusk=”selector-here” syntax; recommended $browser->click(‘@expand-nav’); As you can see, adding the dusk attribute to your page elements allows you to refer‐ ence them directly in a way that won’t change when the display or layout of the page changes later; when any method asks for a selector, pass in the @ sign and then the content of your dusk attribute. Let’s take a look at a few of the methods you can call on $browser. To work with text and attribute values, use these methods: value($selector, $value = null) Returns the value of any text input if only one parameter is passed; sets the value of an input if a second parameter is passed. text($selector) Gets the text content of a nonfillable item like a