Pushing Laravel further — best tips & good practices for Laravel 5.7

Pushing Laravel further — best tips & good practices for Laravel 5.

7Alex RenokiBlockedUnblockFollowFollowingJan 2Laravel is already known by many PHP developers for writing clean, working and debugg-able code.

It also has support for many many features, that sometimes aren’t listed in the docs, or they were, but they were removed for various reasons.

I have been working with Laravel in production-usage for 2 years and I have learned from writing bad code to better code and I have taken the advantage of Laravel since the first time I started wrote code with it.

I am going to show you the mysterious tricks that might help you when writing code with Laravel.

Use local scopes when you need to query thingsLaravel has a nice way to write queries for your database driver using Query Builder.

Something like this:$orders = Order::where('status', 'delivered')->where('paid', true)->get();This is pretty nice.

This made me give up on SQL and focus on coding that is more approachable for me.

But this bit of code can be better written if we use local scopes.

Local scopes allow us to create our own Query Builder methods we can chain when we try to retrieve data.

For example, instead of ->where() statements, we can use ->delivered() and ->paid() in a cleaner way.

First, in our Order model, we should add some methods:class Order extends Model{ .

public function scopeDelivered($query) { return $query->where('status', 'delivered'); } public function scopePaid($query) { return $query->where('paid', true); }}When declaring local scopes, you should use the scope[Something] exact naming.

In this way, Laravel will know that this is a scope and will make use of it in your Query Builder.

Make sure you include the first argument that is automatically injected by Laravel, and is the query builder instance.

$orders = Order::delivered()->paid()->get();For more dynamic retrieval, you can use dynamic local scopes.

Each scope allows you to give parameters.

class Order extends Model{ .

public function scopeStatus($query, string $status) { return $query->where('status', $status); }}$orders = Order::status('delivered')->paid()->get();Later in this article, you’ll learn why you should use snake_case for database fields, but here is the first reason: Laravel uses by default where[Something] to replace the previously scope.

So instead of the previously one, you can do:Order::whereStatus('delivered')->paid()->get();Laravel will search for the snake_case version of Something from where[Something].

If you have status in your DB, you will use the previous example.

If you have shipping_status, you can use:Order::whereShippingStatus('delivered')->paid()->get();It’s your choice!Magic scopesWhen building things, you can use the magic scopes that are already embeddedRetrieve the results by created_at , descending:User::latest()->get();Retrieve the results by any field, descending:User::latest('last_login_at')->get();Retrieve results in random order:User::inRandomOrder()->get();Use Relationships to avoid big queries (or bad-written ones)Have you ever used a ton of joins in a query just to get more info?.It is pretty hard to write those SQL commands, even with Query Builder, but models already do that with Relationships.

Maybe you won’t be familiar with at first, due to high-amount of information that the documentation provides, but this will help you understand better how the things work and how to make your application run smoother.

Check Relationships’ documentation here.

Use Jobs for time-consuming tasksLaravel Jobs are a powerful must-to tool to run tasks in the background.

Do you want to send an email?.Jobs.

Do you want to broadcast a message?.Jobs.

Do you want to process images?.Jobs.

Jobs help you give up on loading time for your users on consuming-time tasks like these.

They can be put into named queues, they can be prioritized, and guess what — Laravel implemented queues almost everywhere where was possible: either processing some PHP in the background or sending notifications or broadcasting events, queues are there!You can check Queues’ documentation here.

I love to use Laravel Horizon for queues since it’s easy to set up, it can be daemonized using Supervisor and through the configuration file, I’m able to tell Horizon how many processes I want for each queue.

Stick to database standards & AccessorsLaravel teaches you from the very beginning that your variables and methods should be $camelCase camelCase() while your database fields should be snake_case.

Why?.Because this helps us build better accessors.

Accessors are custom fields we can build right from our model.

If our database contains first_name, last_name and age, we can add a custom field named name that concatenates the first_name and last_name.

Don’t worry, this won’t be written in the DB by any means.

It’s just a custom attribute this specific model has.

All accessors, like scopes, have a custom naming syntax: getSomethingAttribute:class User extends Model{ .

public function getNameAttribute(): string { return $this->first_name.

' '.

$this->last_name; }}When using $user->name, it will return the concatenation.

By default, the name attribute is not shown if we dd($user), but we can make this generally available by using the $appends variable:class User extends Model{ protected $appends = [ 'name', ]; .

public function getNameAttribute(): string { return $this->first_name.

' '.

$this->last_name; }}Now, each time we dd($user) , we will see that the variable is there (but still, this is not present in the database)Be careful, however: if you already have a name field, the things are a bit different: the name inside $appends is not longer needed, and the attribute function waits for one parameter, which is the already stored variable (we will no longer use $this).

For the same example, we might want to ucfirst() the names:class User extends Model{ protected $appends = [ // ]; .

public function getFirstNameAttribute($firstName): string { return ucfirst($firstName); } public function getLastNameAttribute($lastName): string { return ucfirst($lastName); }}Now, when we use $user->first_name, it will return an uppercase-first string.

Due to this feature, it’s good to use snake_case for your database fields.

Do not store model-related static data in configsWhat i love to do is to store model-related static data inside the model.

Let me show you.

Instead of this:BettingOdds.

phpclass BettingOdds extends Model{ .

}config/bettingOdds.

phpreturn [ 'sports' => [ 'soccer' => 'sport:1', 'tennis' => 'sport:2', 'basketball' => 'sport:3', .

],];And accessing them using:config(’bettingOdds.

sports.

soccer’);I prefer doing this:BettingOdds.

phpclass BettingOdds extends Model{ protected static $sports = [ 'soccer' => 'sport:1', 'tennis' => 'sport:2', 'basketball' => 'sport:3', .

];}And access them using:BettingOdds::$sports['soccer'];Why?.Because it’s easier to be used in further operations:class BettingOdds extends Model{ protected static $sports = [ 'soccer' => 'sport:1', 'tennis' => 'sport:2', 'basketball' => 'sport:3', .

]; public function scopeSport($query, string $sport) { if (!.isset(self::$sports[$sport])) { return $query; } return $query->where('sport_id', self::$sports[$sport]); }}Now we can enjoy scopes:BettingOdds::sport('soccer')->get();Use collections instead of raw-array processingBack in the days, we were used to work with arrays in a raw way:$fruits = ['apple', 'pear', 'banana', 'strawberry'];foreach ($fruits as $fruit) { echo 'I have '.

$fruit;}Now, we can use advanced methods that will help us process the data within arrays.

We can filter, transform, iterate and modify data inside an array:$fruits = collect($fruits);$fruits = $fruits->reject(function ($fruit) { return $fruit === 'apple';})->toArray();['pear', 'banana', 'strawberry']For more details, check the extensive documentation on Collections.

When working with Query Builders, the ->get() method returns a Collection instance.

But be careful to not confuse Collection with a Query builder:Insde the Query Builder, we did not retrieve any data.

We have a lot of query-related methods: orderBy(), where(), etc.

After we hit ->get(), the data is retrieved, the memory has been consumed, it returns a Collection instance.

Some Query Builder methods are not available, or they are, but they name is different.

Check the Available Methods for more.

If you can filter data at the Query Builder level, do it!.Do not rely on filtering when it comes to the Collection instance — you will use too much memory in some places and you don’t want to.

Limit your results and use indexes at the DB level.

Use packages and don’t reinvent the wheelHere are some packages I use:Laravel Blade DirectivesLaravel CORS (protect your routes from other origins)Laravel Tag Helper (better usage of HTML tags in Blade)Laravel Sluggable (useful when it comes to generating slugs)Laravel Responder (build a JSON API easier)Image Intervention (handle images in style)Horizon (supervisoring queues with minimal configuration)Socialite (minimal configuration to login with social media)Passport (OAuth implementation for routes)Spatie’s ActivityLog (track activity for models)Spatie’s Backup (backup files and databases)Spatie’s Blade-X (define your own HTML tags; works good with Laravel Tag Helper)Spatie’s Media Library (easy way to attach files to models)Spatie’s Response Cache (cache controller responses)Spatie’s Collection Macros (more macros on collections)Here are some packages I have written:Befriended (like, follow and block like in social media)Schedule (create timetables and check against hours and days)Rating (rate models)Guardian (permissions system, the easy way)Too hard to understand?.Reach me!If you have more questions about Laravel, if you need help with any information related to DevOps or simply want to say a Thank you!, you can find me on Twitter @rennokki!.

. More details

Leave a Reply