Lessons from Laracasts, Part 2
27 April 2018This is part 2 of my collection of tips I've taken from Let's Build a Forum with Laravel and TDD, the mega-tutorial (102 lessons) by Jeffrey Way on his Laracasts site. Part 1 is here. This post contains 51 tips, covering lessons 43-102.
51.
The artisan notifications:table command creates a migration for a table to hold database notifications. [Source]
52.
The tap helper, inspired by Ruby can help refactoring by removing temporary variables. [Source]
53.
Use the artisan event:generate command to create events and listeners. [Source]
54.
The Notification::fake and Notification:assertSentTo methods allow you to test notifications without triggering their usual behaviour. [Source]
55.
JW uses [identifier]CacheKey as a method to supply a single point of truth for a cache's key. [Source]
56.
An example of using Laravel's container when refactoring spam inspection methods to their own classes: [Source], around 06:35.
57.
The resolve method resolves a class out of Laravel's container. [Source]
58.
JW uses a 422 Unprocessable Entity HTTP status code when catching exceptions thrown during AJAX calls. [Source]
59.
Artisan's make:rule command will scaffold the necessary code for custom validation rules. [Source]
60.
The throttle middleware rate limits access to Laravel routes. But JW notes that failing validation would make this solution problematic. [Source]
61.
Use form requests for more complex form situations requiring authorization and validation. [Source]
62.
The collect helper converts arrays to collections. [Source]
63.
When the filter method is called without an argument on a collection it removes falsey values. [Source]
64.
In the tutorial, JW uses the At.js library to add mentions autocomplete functionality to the forum. [Source]
65.
Use the pluck method to get the values for a given key from a collection. JW uses it to return values from a collection retrieved by Eloquent:
return User::where('name', 'LIKE', '$search%')->take(5)->pluck('name');[Source]
66.
One way to grab CSS from an NPM package: use the copy method in your webpack.mix.js file:
mix.js('resources/assets/js/app.js', 'public.js')
.sass('resources/assets/sass/app.scss', 'public.css')
.copy('node_modules/[path_to_src_css_file]', '[path_to_dest_css_file]');[Source]
67.
Another way to grab a package's CSS: import it in your main scss file. [Source]
68.
Another way to grab a package's CSS: just manually copy it to your css directory. [Source]
69.
To import CSS files depending on the view, use a @yield('head') directive, then in the template you want to load this file, include the link tag in a @section('head') directive. [Source]
70.
The UploadedFile::fake method allows you to fake a file (or image) for testing:
UploadedFile::fake()->image('filename.jpg', $width, $height)[Source]
71.
The Storage::fake method will create a fake storage disk for testing. E.g.:
Storage::fake('public');
$this->json('POST', 'api/upload', [
'file' => UploadedFile::fake()->image('filename.jpg')
]);
Storage::disk('public')->assertExists('filename.jpg');[Source]
72.
hashName returns the filename for an uploaded file. [Source]
73.
Compare Eloquent models with is and isNot. [From the comments to Source]
74.
How did I not know about this? The input tag has an accept attribute which takes a comma-separated list of content type specifiers, either file extensions, MIME types or audio/*, video/* or image/*. [Source]
76.
Any native attribute (e.g. name on a form element) specified on a Vue component instance will be passed to that component, when the root element of the component is a single form element. [Source]
77.
As of PHP 7, you can use the null coalescing operator to assign a default value. E.g.:
return Redis::get($this->cacheKey()) ?? 0;[Source]
78.
The steps to create a custom middleware:
-
php artisan make:middleware NameOfMiddleware -
In the
handlemethod of the new middleware write the code to filter access to the route:
if (! $request->user()->confirmed) {
return redirect('/threads');
}- List the middleware class in
app/Http/Kernel.php:
protected $routeMiddleware = [
'name-of-middleware' => NameOfMiddleware::class
];- Attach the middleware to a route:
Route::post('threads', 'ThreadsController@store')->middleware('name-of-middleware');[Source]
79.
To simulate a new user registration in a test you can call the event that's called by the register method in the RegistersUsers trait:
event(new Registered($user));[Source]
80.
Faking emails while testing can be done with the Mail::fake method.
Mail::fake();
[test code here]
Mail::assertSent(MailableName::class);[Source]
81.
Eloquent models have a $cast property that, in the words of the docs, "provides a convenient method of converting attributes to common data types". Useful, for example, for converting 0 or 1 to booleans. [Source]
82.
The forceCreate Eloquent method is like create except that it ignores any mass-assignment rules. [Source]
83.
You can define modifications of your model factories with the state method. [Source]
84.
Laravel's str-slug helper converts strings to slugs. [Source]
85.
Use the query builder's max method to return the highest value of a given model field. E.g.:
$max = Book::wherePublisher('foobar')->max('pages');[Source]
86.
As of PHP 7, you can access characters in strings using an offset in array-type brackets. To access the final character of string, use -1:
$final = $slug[-1];[Source]
87.
Use the abort_if helper to throw an exception under a given boolean condition. [Source]
88.
You can render an array as JSON in a blade template with the @json directive. [Source]
89.
MySQL has a SET NULL action that can be set for UPDATE and DELETE operations on a foreign key. In a Laravel migration, this reads as:
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('set null');[Source]
90.
If you get a "Cannot add foreign key constraint" error when migrating, that can be because the migration where you're setting the foreign key has a timestamp earlier than the migration for the table you're referencing in your foreign key. JW fixes this by manually altering the timestamp of the migration to a later date. [Source]
91.
A potential error when using foreign keys and testing with an SQLite database: foreign keys are not enabled by default. To enable them, in your test include:
DB::statement('PRAGMA foreign_keys = ON');[Source]
NB. A comment to this video notes that instead you can use:
Schema::enableForeignKeyConstraints();92.
Use double negation (!!) to cast variables to a boolean. [Source]
93.
Use the @php directive to embed PHP code in Blade views. [Source]
94.
It is recommended not to reference the .env file within the application. Instead, pull environment variables from a configuration file (e.g. config/services.php) which itself pulls from .env. [Source]
95.
Instead of getting the remote IP address with $_SERVER['REMOTE_ADDR'], use request()->ip(). [Source]
96.
Use app()->runningUnitTests() for behaviour conditional on whether you're running your tests. [Source]
97.
As of Laravel 5.5, validation returns the validated data, which makes it possible to create concise flows like this:
$thread->update(request()->validate([
'title' => 'required',
'body' => 'required'
]));[Source]
98.
Using an external search service like Algolia can slow down tests. To circumvent this, set SCOUT_DRIVER to null in your test environment variables (phpunit.xml). For tests where you want to use the search service, you can update the config for that specific test:
config(['scout.driver' => 'algolia']);[Source]
99.
For tests which use a search service, remember to remove any created elements at the end of the test to avoid false positives, with the unsearchable method on a collection. [Source]
100.
Specify the data sent to your search service by overriding toSearchableArray in your model. [Source]
101.
Any property passed to a Vue component that isn't accepted as a property in that component, will be assigned to the top-level element in that component's template. [Source]
102.
When you want to display sanitized data in a Vue (or other front-end framework) component, and you want to sanitize it with PHP, you can do it with a custom accessor. [Source]