Laravel Best Practices
Owner: Development Last revision: 12.11.2021
Naming Conventions#
Naming things is often seen as one of the harder things in programming. That’s why we’ve established some high level guidelines for naming classes.
Commands#
The names given to artisan commands should all be kebab-cased.
# Good
php artisan delete-old-records
# Bad
php artisan deleteOldRecords
Configuration files#
Configuration files must use kebab-case.
config/
pdf-generator.php
Configuration keys must use snake_case.
// config/pdf-generator.php
return [
'chrome_path' => env('CHROME_PATH'),
];
Avoid using the env
helper outside of configuration files. Create a configuration value from the env
variable like above.
Controllers#
Generally controllers are named by the plural form of their corresponding resource and a Controller suffix. This is to avoid naming collisions with models that are often equally named.
e.g. UsersController
or PostsController
Try to keep controllers simple and stick to the default CRUD keywords (index, create, store, show, edit, update, destroy). Extract a new controller if you need other actions.
In the following example, we could have PostsController@favorite
, and PostsController@unfavorite
, or we could extract it to a separate FavoritePostsController
.
class PostsController
{
public function create()
{
// ...
}
// ...
public function favorite(Post $post)
{
request()->user()->favorites()->attach($post);
return response(null, 200);
}
public function unfavorite(Post $post)
{
request()->user()->favorites()->detach($post);
return response(null, 200);
}
}
Here we fall back to default CRUD words, create and destroy.
class FavoritePostsController
{
public function create(Post $post)
{
request()->user()->favorites()->attach($post);
return response(null, 200);
}
public function destroy(Post $post)
{
request()->user()->favorites()->detach($post);
return response(null, 200);
}
}
This is a loose guideline that doesn’t need to be enforced.
When writing non-resourceful controllers you might come across invokable controllers that perform a single action. These can be named by the action they perform again suffixed by Controller.
e.g. PerformCleanupController
Events#
Events will often be fired before or after the actual event. This should be very clear by the tense used in their name.
E.g. ApprovingLoan
before the action is completed and LoanApproved
after the action is completed.
Form Requests#
Always name your form requests by the singular name of your resource, suffixed with Request
.
e.g. PostRequest
In some cases, you need separate form requests for the create()
and update()
controller methods. In these cases put the method name between the name of your resource and the Request
suffix.
e.g. PostCreateRequest
or PostUpdateRequest
Jobs#
A job’s name should describe its action.
E.g. CreateUser
or PerformDatabaseCleanup
Listeners#
Listeners will perform an action based on an incoming event. Their name should reflect that action with a Listener
suffix. This might seem strange at first but will avoid naming collisions with jobs.
E.g. SendInvitationMailListener
Mailables#
Again to avoid naming collisions we’ll suffix mailables with Mail
, as they’re often used to convey an event, action or question.
e.g. AccountActivatedMail
or NewEventMail
Models#
Always name your models by the singular name of your resource.
e.g. User
or Post
Notifications#
Again to avoid naming collisions we’ll suffix notifications with Notification
, as they’re often used to convey an event, action or question.
e.g. AccountActivatedNotification
or NewEventNotication
Tests#
Function names of tests cases should always begin with test
.
public function testIfPageReturns200(): void
{
$response = $this->json('POST', 'page', [
'somedata' => 'Data',
]);
$response->assertStatus(200);
}
Views#
View files must use kebab-case.
resources/
views/
pages/
cookies-policy.blade.php
Commands#
A command should always give some feedback on what the result is. Minimally you should let the handle
method spit out a comment at the end indicating that all went well.
// in a Command
public function handle()
{
// do some work
$this->comment('All ok!');
}
If possible use a descriptive success message eg. Old records deleted
.
Facades#
Whenever possible, avoid using facades in controllers, models or actions.
Instead, use a the available helper function or inject the underlying class into your object. Use the Facade Class Reference to find the underlying class.
// Good
session(['cart' => $data]);
// Bad
Session::put('cart', $data);
// Good
use Illuminate\Database\DatabaseManager;
class MyClass
{
protected $db;
public function __construct(DatabaseManager $db)
{
$this->db = $db;
}
public function getComplexQueryResult()
{
return $this->db->raw("...");
}
}
// Bad
class MyClass
{
public function getComplexQueryResult()
{
return DB::raw("...");
}
}
Routing#
Public-facing urls must use kebab-case.
https://gofurther.digital/en/custom-software-development
https://gofurther.digital/en/blog/posts/digital-transformation-pt1-introduction
Route names must use kebab-case with dot notation.
// Good
Route::get('custom-software-development', 'CustomSoftwareDevelopmentController@show')
->name('custom-software-development');
Route::get('blog/posts/{blogPath}', 'BlogController@show')->name('blog.show');
// Bad
Route::get('custom-software-development', 'CustomSoftwareDevelopmentController@show')
->name('customSoftwareDevelopment');
Route::get('blog/posts/{blogPath}', 'BlogController@show')->name('blog-show');
When defining routes, use method chaining instead of the array notation.
// Good
Route::get('custom-software-development', 'CustomSoftwareDevelopmentController@show')
->name('custom-software-development');
// Bad
Route::get('custom-software-development', [
'as' => 'custom-software-development',
'uses' => 'CustomSoftwareDevelopmentController@show',
]);
All routes have an http verb, that’s why we like to put the verb first when defining a route. It makes a group of routes very readable. Any other route options should come after it.
// Good
Route::get('custom-software-development', 'CustomSoftwareDevelopmentController@show')
->name('custom-software-development');
// Bad
Route::name('custom-software-development')
->get('custom-software-development', 'CustomSoftwareDevelopmentController@show');
Route parameters should use camelCase.
// Good
Route::get('blog/posts/{blogPath}', 'BlogController@show')->name('blog.show');
// Bad
Route::get('blog/posts/{blog_path}', 'BlogController@show')->name('blog.show');
A route url should not start with /
unless the url would be an empty string.
// Good
Route::get('/', 'HomeController@show')->name('home');
Route::get('custom-software-development', 'CustomSoftwareDevelopmentController@show')
->name('custom-software-development');
// Bad
Route::get('', 'HomeController@show')->name('home');
Route::get('/custom-software-development', 'CustomSoftwareDevelopmentController@show')
->name('custom-software-development');
If your route only needs to return a view, you may use the Route::view
method.
// Good
Route::view('privacy-policy', 'pages.privacy')->name('privacy');
// Bad
Route::get('privacy-policy', 'PrivacyPolicyController@show')->name('privacy');
Single Responsibility#
A controller must only have the responsibility to return a response. Move business logic from controllers to injectable action classes.
// Good
public function store(Request $request)
{
$this->saveArticleImageAction->execute($request->file('image'));
// ...
}
class SaveArticleImageAction implements ActionInterface
{
public function execute(...$args)
{
if (!is_null($args[0])) {
$image->move(public_path('images') . 'temp');
}
}
}
// Bad
public function store(Request $request)
{
if ($request->hasFile('image')) {
$request->file('image')->move(public_path('images') . 'temp');
}
// ...
}
Validation#
In most cases it is preferable to move validation in separate form request classes.
// Good
public function store(PostRequest $request)
{
// ...
}
class PostRequest extends Request
{
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
];
}
}
// Bad
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);
// ...
}
When using multiple rules for one field in a form request, avoid using |
, always use array notation. Using an array notation will make it easier to apply custom rule classes to a field.
// Good
public function rules()
{
return [
'email' => ['required', 'email'],
];
}
// Bad
public function rules()
{
return [
'email' => 'required|email',
];
}
Custom validation rules should be always defined using a rule object instead of closures or extending the Validator
facade.