In this article
-
Introduction
- 1. Using DATETIME Fields Instead of Booleans
- 2. Naming Date and Time Columns
- 3. Using Matrix Sequences in Factories
- 4. Using 'when' in PendingRequest
- 5. Using 'checked' and 'selected' Blade Directives
- 6. Using 'Mockery::on()' in PHPUnit tests
- 7. Using getOrPut() in Collections
- 8. Debugging HTTP Requests
- 9. Replicating Models
- 10. Adding Auto-complete Hints to Artisan Commands
- 11. Using 'wasRecentlyCreated' on Models
- 12. Changing the Key for Route Model Binding
- 13. Using Dependency Injection
- 14. Filtering Collections by Class Type
- 15. Defining Custom Temporary URL Logic
- 16. Validating MAC Addresses
- 17. Encrypting Fields in the Database
- 18. Moving Logic into Methods
- 19. Using the Different Maintenance Mode Options
- 20. Using Readonly Properties
- Conclusion
Introduction
I regularly post little code snippets and tips on Twitter (@AshAllenDesign) that you can use in your Laravel applications. But, due to the nature of the social media site, older content can sometimes get swept away and not seen after the first few days of posting it.
So, I thought I'd put together a list of my favourite 20 tips that I've posted on my social media accounts for you all to see.
They aren't in any particular order, but, I'm hoping there'll be at least one new thing in here that you will find useful and learn from.
1. Using DATETIME Fields Instead of Booleans
In your Laravel apps, you can sometimes use DATETIME
fields instead of booleans.
For example, if we had a Post
model, instead of it having a boolean is_published
field, you could use a DATETIME
field called published_at
. By using this approach, you can still check if a field is published (using a method or accessor) and also get extra information to see exactly when the post was published.
You might need the exact date in your app now, but it might be something that will be useful in future features (such as reporting).
For example, we could update the Post
model to look like so:
1class Post extends Model2{3 public function isPublished(): bool4 {5 return $this->published_at !== null;6 }7}
This means that we would now be able to use the $post->isPublished()
to get a boolean value. And, we would be able to use $post->published_at
to get the date and time that the post was published at.
The Laravel framework itself actually uses this type of approach for soft deleting models and sets the deleted_at
field when a model is soft deleted.
2. Naming Date and Time Columns
It can be useful to use the "action_at" naming convention for your DATETIME
and TIMESTAMP
fields. This helps you to instantly recognise if a field that you're working with is a datetime field (and likely a Carbon
instance if you've casted to that).
For example, instead of using fields like:
- publish_time
- verified date
- password_reset
It could be more beneficial to rename them to:
- publish_at
- verified at
- password reset at
3. Using Matrix Sequences in Factories
You can use a MatrixSequence
in your model factories to create extra data for your tests:
For example, you could run the following code:
1User::factory(4)2 ->state(3 new MatrixSequence(4 [['first_name' => 'John', 'last_name' => 'Jane']],5 [['first_name' => 'Alice', 'last_name' => 'Bob']],6 ),7 )8 ->create();
Running the above code could create 4 User
models with the following fields:
-
first_name
: John,last_name
: Alice -
first_name
: John,last_name
: Bob -
first_name
: Jane,last_name
: Alice -
first_name
: Jane,last_name
: Bob
4. Using 'when' in PendingRequest
You can use the when
method on the PendingRequest
class when building your requests using the Http
facade.
For example, let's say that you had the following code:
1$http = Http::withBasicAuth($username, $password);2 3if (app()->environment('local')) {4 $http->withoutVerifying();5}
If you prefer to keep your request logic bundled together and not separated using an if
statement, you could rewrite it using when
like so:
1$http = Http::withBasicAuth($username, $password)2 ->when(app()->environment('local'), function (PendingRequest $request) {3 $request->withoutVerifying();4 });
There's no right or wrong answer with using either of these approaches and it's all down to personal preference of how you prefer to write your code.
5. Using 'checked' and 'selected' Blade Directives
When you're creating forms in Blade, there might be times when you want to have a checkbox checked on page load or an option selected in a select
element. To do this, you can use the @checked
and @selected
Blade directives.
For example, if we wanted a checkbox to be selected if a user is active, we could use the following approach:
1<input type="checkbox"2 name="active"3 value="active"4 @checked($user->active) />
Likewise, we could also use this same approach if we wanted to choose an option in a select
element using the @selected
directive:
1<select name="version">2 @foreach ($product->versions as $version)3 <option value="{{ $version }}" @selected(old('version') == $version)>4 {{ $version }}5 </option>6 @endforeach7</select>
6. Using 'Mockery::on()' in PHPUnit tests
When writing your tests in Laravel and PHP, you might want to mock a class (such as a service or action). By mocking a class, you can assert that it was called as expected with the right arguments but without running the code inside it. This can be particularly useful to do when testing your controllers.
Sometimes, you may want to make your tests a bit stricter and assert that a specific model or object is passed to the method. To do this, you can Mockery::on()
.
As a basic example, let's take the following controller. The controller method executes the PublishPost
action, with the action accepting the Post
model. We then return a simple JSON response.
app/Http/Controllers/PublishPostController
1use App\Actions\PublishPost;2use App\Models\Post;34class PublishPostController extends Controller5{6 public function __invoke(Post $post, PublishPost $action): Response7 {8 $action->execute($post);910 return response()->json([11 'success' => true,12 ]);13 }14}
In our test, we can set up our mocked action before we make our HTTP call. In the mock expectations, we can say that that we expect the execute
method to be called once and to be passed the Post
model that we specify.
If these criteria are met, the assertions will pass and we will know that the action was called as we expected. Otherwise, if the criteria isn't met, the test will fail.
So, our test could look something like this:
tests/Feature/PublishPostControllerTest
1use App\Actions\PublishPost;2use App\Models\Post;3use Mockery;4use Mockery\MockInterface;5use Tests\TestCase;67class PublishPostControllerTest extends TestCase8{9 /** @test */10 public function post_can_be_deleted(): void11 {12 $post = Post::factory()->create();1314 $this->mock(PublishPost::class, function (MockInterface $mock) use ($post) {15 $mock->shouldReceive('execute')16 ->once()17 ->withArgs([18 Mockery::on(fn (Post $arg): bool => $arg->is($post)),19 ]);20 });2122 $this->post(route('post.publish', $post))23 ->assertOk()24 ->assertJson([25 'success' => true,26 ]);27 }28}
If you're interested in reading more about testing, you might be interested in reading my How to Make Your Laravel App More Testable article.
7. Using getOrPut() in Collections
There's a useful getOrPut
method that you can use on your Collections. It can be used to fetch an item (if it already exists), or insert it and fetch it if it doesn't.
This can be really useful when building up a Collection with data from multiple sources and don't want duplicate items in your data.
For example, instead of writing:
1if (! $collection->has($key)) {2 $collection->put($key, $this->builtItem($data));3}4 5return $collection->get($key);
You can use the getOrPut
method like so:
1return $collection->getOrPut($key, fn () => $this->buildItem($data));
8. Debugging HTTP Requests
When making HTTP requests from your Laravel application using the Http
facade, you might want to dump the request. This can be extremely useful for debugging and I use this myself during development when building and troubleshooting requests to external APIs.
To dump the request data, you can use the the dump
method like so:
1Http::dump()->get($url);
Likewise, you can use the dd
method to die and dump the request like so:
1Http::dd()->get($url);
9. Replicating Models
In your Laravel apps, you can duplicate a model using the replicate()
method. This makes it really simple to make copies of models.
I use this functionality in my own blog to makes copies of a generic blog post template that I use when starting a new article.
For example, you can duplicate a model like so:
1$post = Post::find(123);2 3$copiedPost = $post->replicate();4 5$copiedPost->save();
If you want to exclude properties from being copied, you can pass the field names as an array into the method:
1$post = Post::find(123);2 3$copiedPost = $post->replicate([4 'author_id',5]);6 7$copiedPost->save();
Additionally, the replicate
method creates an unsaved model, so you can chain your usual model methods together. For example, we can copy the model and add " (copy)" on to the end of the title so that we can see that its been replicated.
1$post = Post::find(123);2 3$copiedPost = $post->replicate([4 'author_id',5])->fill([6 'title' => $post->title.' (copy)',7]);8 9$copiedPost->save();
It's important to remember that the model's aren't saved in the database after using the replicate
method. So, you'll need to make sure you persist them using the save
method.
10. Adding Auto-complete Hints to Artisan Commands
When you're building your Artisan commands in your Laravel apps, you can use the anticipate
method to provide auto-completion hints for the user.
For example, we could have a command that finds a user using their email. In this command, we could pass a list of the possible emails to the anticipate
method so that they are displayed to the display as they start typing, like so:
1class TestCommand extends Command 2{ 3 public function handle() 4 { 5 $email = $this->anticipate('Find user by email: ', [ 6 'mail@ashallendesign.co.uk', 7 'hello@example.com', 8 ]); 9 }10}
It's worth noting that a user can still input an answer that isn't in the anticipated list. The anticipate
method is only used for providing hints and not for validating input.
11. Using 'wasRecentlyCreated' on Models
In Laravel, there may be times when you need to check if a model was fetched from the database or just created in the current request lifecycle - like when using the firstOrCreate
method.
To do this, you can use the ->wasRecentlyCreated
field on the model like so:
1$user = User::firstOrCreate( 2 ['email' => request('email')], 3 ['name' => request('name')], 4); 5 6if ($user->wasRecentlyCreated) { 7 // Your user was just created... 8} else { 9 // Your user already existed and was fetched from the database...10}
12. Changing the Key for Route Model Binding
In your Laravel app's routes, you can change the key that is used to resolve the models using route model binding.
For example, let's say that you have a route that accepts a blog post's slug:
1Route::get('blog/{slug}', [BlogController::class, 'show']);
In our controller, we would then need to manually try and find the blog post inside the controller method like so:
1use App\Actions\PublishPost; 2use App\Models\Post; 3 4class BlogController extends Controller 5{ 6 public function show($slug) 7 { 8 $post = Post::where('slug', $slug)->firstOrFail(); 9 10 return view('blog.show', [11 'post' => $post,12 ]);13 }14}
However, to simplify this code and clean it up, we could update the slug
parameter to be post:slug
instead:
1Route::get('blog/{post:slug}', [BlogController::class, 'show']);
This now means that we can now update our controller method to expect a Post
model in our controller method called post
, and Laravel will automatically find the Post
model that has the slug passed in the URL.
This means that we don't need to manually find the model in our controller anymore and we can let Laravel resolve it for us like so:
1use App\Actions\PublishPost; 2use App\Models\Post; 3 4class BlogController extends Controller 5{ 6 public function show(Post $post) 7 { 8 return view('blog.show', [ 9 'post' => $post,10 ]);11 }12}
13. Using Dependency Injection
It can be useful to use dependency injection in your Laravel code. It allows you to resolve your dependencies from the container whenever you try and instantiate a new class. This can help you to create more maintainable and testable code by making things like mocking much simpler to use.
For example, in this example, we aren't using dependency injection and are just creating a new class:
1class MyController extends Controller2{3 public function __invoke()4 {5 $service = new MyService();6 7 $service->handle();8 }9}
Now, we 've removed the first line inside the method and added MyService
as anither method parameter. Laravel will then "inject" the service for us whenever this method is called so that we can use it:
1class MyController extends Controller2{3 public function __invoke(MyService $service)4 {5 $service->handle();6 }7}
There might also be times when you're inside a class of code and it turns out that without some major refactoring that you won't be able to inject your class by passing it as an extra method argument. In these cases, you can use the resolve
helper function that Laravel provides like so:
1class MyClass2{3 public function execute()4 {5 $service = resolve(MyService::class);6 7 $service->handle();8 }9}
14. Filtering Collections by Class Type
You can filter Laravel Collections by a given class type using the whereInstanceOf
method. This can be useful to do if you are building Collections from multiple sources of data (such as polymorphic relationships) and need to filter it down to specific classes.
For example, we could have the following Collection and filter it down to only contain User
classes:
1$collection = collect([2 new User(),3 new User(),4 new User(),5 new Comment(),6]);7 8$filtered = $collection->whereInstanceOf(User::class)->all();
The whereInstanceOf
method also accepts an array of classes if you want to filter by more than one class at a time. For example, to filter the Collection to only contain Post
and Comment
classes, you could it like so:
1$filtered = $collection->whereInstanceOf([Post::class, Comment::class])->all();
15. Defining Custom Temporary URL Logic
You can customize how temporary URLs are created for a specific storage disk. This can be handy if you have a controller that allows you to download files stored via a disk that doesn't typically support temporary URLs.
To use this functionality, you'll first need to register the logic (usually in a service provider) using the buildTemporaryUrlsUsing
method.
For example, here's an example of how we could register some custom logic for building temporary URLs for the local
storage disk:
1public function boot() 2{ 3 Storage::disk('local')->buildTemporaryUrlsUsing(function ($path, $expiration, $options) { 4 return URL::temporarySignedRoute( 5 'files.download', 6 $expiration, 7 array_merge($options, ['path' => $path]), 8 ); 9 });10}
After the logic has been registered, you'll then be able to use this functionality through the temporaryUrl
method like so:
1$tempUrl = Storage::disk('local')->temporaryUrl('file.jpg', now()->addMinutes(5));
16. Validating MAC Addresses
There's a useful mac_address
rule available in Laravel that you can use to validate whether a field is a valid MAC address.
For example, the following validation would pass because the value is a valid MAC address:
1Validator::make([2 'device_mac' => '00:1A:C2:7B:00:47'3], [4 'device_mac' => 'mac_address',5])->passes();
But the following validation would fail because the value is not a valid MAC address:
1Validator::make([2 'device_mac' => 'invalid-mac-address'3], [4 'device_mac' => 'mac_address',5])->passes();
17. Encrypting Fields in the Database
You can store model fields in an encrypted format in your database using the encrypted
cast. This can be handy if you're storing private data in your database that needs some form of protection in case of a database breach.
For example, to encrypt the my_encrypted_field
on a User
model, you could update your model like so:
1class User extends Authenticatable2{3 protected $casts = [4 'my_encrypted_field' => 'encrypted',5 ];6}
You can still carry on using the field as usual. For example, to update the value stored in the my_encrypted_field
, we could still use the update
method as usual:
1$user->update(['my_encrypted_field' => 'hello123']);
If you were to now look in the database, you wouldn't see hello123
in the user's my_encrypted_field
field. Instead, you'd see an encrypted version of it.
But, you can stil use the original value in your code without needing to make any changes:
1$result = $user->my_encrypted_field;2 3// $result is equal to: "hello123"
It's important to remember that the encryption makes use of the application's APP_KEY
, so if if this is compromised in a breach or is changed, it will be possible to decrypt the encrypted fields stored in the database.
18. Moving Logic into Methods
In your code, instead of directly checking fields in your conditionals, it can sometimes help to move the logic into a method.
It can help to improve readability and can also keep your code DRY if you need to reuse the same logic (or if the logic changes in the future).
For example, let's say that we want to check if a user is approved. Our code might look like so:
1if ($user->approved === Approval::APPROVED) {2 // Do something...3}
But, the main issue with this approach is that if we are using it in multiple places in the codebase, it will be cumbersome to update if the logic needs to change.
So, we can move the logic into a method (such as isApproved
) on the User
model. Now, we can call that method instead of checking the field directly.
For example, our model might now look like this:
app/Models/User.php
1class User extends Model2{3 public function isApproved(): bool4 {5 return $this->approved === Approval::APPROVED;6 }7}
This means that we'd then be able to use that method like so in our code like so:
1if ($user->isApproved()) {2 // Do something...3}
19. Using the Different Maintenance Mode Options
Laravel provides several maintenance mode options that can be really handy to use.
To put your app into maintenance mode, you can run the following command:
1php artisan down
You might want to refresh the maintenance page in set intervals so that your users won't need to manually refresh when the site is back up. You can do this using the --refresh
option and passing a time in seconds, like so:
1php artisan down --refresh=30
You might want to give yourself access to the app when it is in maintenance mode for your users. To do this, you can use the --secret
option and pass in a secret.
For example, we could set our secret to your-secret-here
. This would mean that if you then navigated to your-app-domain.com/your-secret-here
, you'd be granted access and be able to view the rest of the application as normal. You can enable maintenance mode using a secret like so:
1php artisan down --secret="your-secret-here"
If you don't want to use the default 503 maintenance page that is provided by Laravel, you can define your own using the --render
option. For example, we might want to use resources/views/errors/maintenance.blade.php
like so:
1php artisan down --render="errors/maintenance.blade.php"
20. Using Readonly Properties
In PHP 8.1, you can use "readonly properties". These are extremely useful for making DTOs (data transfer objects) smaller and easier to read without unnecessary getters.
For example, let's take a look at how a DTO might look without readonly properties:
1class StoreUserDTO 2{ 3 public function __construct( 4 private string $name, 5 private string $email, 6 private string $password, 7 ) { 8 // 9 }10 11 public function getName(): string12 {13 return $this->name;14 }15 16 public function getEmail(): string17 {18 return $this->email;19 }20 21 public function getPassword(): string22 {23 return $this->password;24 }25}
Now, let's take a look at the same DTO using readonly properties instead:
1class StoreUserDTO 2{ 3 public function __construct( 4 public readonly string $name, 5 public readonly string $email, 6 public readonly string $password, 7 ) { 8 // 9 }10}
I think you'll agree that the second option using the readonly fields looks a lot cleaner and easier to understand at a glance.
Conclusion
Hopefully, this post should have taught you at least one new tip or trick that you can use in your Laravel applications.
If this post helped you out, I'd love to hear about it. Likewise, if you have any feedback to improve this post, I'd also love to hear that too.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter below.
Keep on building awesome stuff! 🚀