Laravel: Convention over Configuration

Ali
4 min readJan 1, 2023

--

To be on the same page here is naming conventions you know and used a gazillion times:

StudlyCase/PascalCase

Example: StudlyCase

camelCase

Example: camelCase

snake_case

Example: snake_case

kebab-case

Example: kebab-case

Why do we need conventions?

Imagine you’ve User and Post models. You want to get all posts of a user. And we've one-to-many relationship between User and Post models.

Without conventions

// Posts table migration
// ...
$table->foreignId('author_id')->constrained();
// ...

Now when we are to define a relationship between User and Post models we have to write something like this:

// User model
// ...
public function posts()
{
return $this->hasMany(Post::class, 'author_id');
}
// ...

See the difference? We have to be explicit about the foreign key name.

With conventions

// Posts table migration
// ...
$table->foreignId('user_id')->constrained();
// ...

Now when we define a relationship between User and Post models it takes the shape of something like this:

// User model
// ...
public function posts()
{
return $this->hasMany(Post::class);
}
// ...

By using the well-defined conventions, we can omit the extra work that the framework already doing for us.

Now the boring part is over. Let’s get to the point.

Naming conventions

Models

Use singular form of the word. Example: User, Post, Comment, Tag. For multi-word models, use PascalCase. Example: UserPost, UserComment, UserTag .

Tables

Use plural form of the word. Example: users, posts, comments, tags. For multi-word table names use snake_case.

Controllers

Use singular form of the word. Example: UserController, PostController; appended Controller suffix.

Routes

Use plural form of the word. Example: users, posts, comments, tags. For multi-word routes use kebab-case.

// get all users
Route::get('users', [UserController::class, 'index'])
->name('users.index');

// show form to create a new user
Route::get('users/create', [UserController::class, 'create'])
->name('users.create');

// create a new user in the database
Route::post('users', [UserController::class, 'store'])
->name('users.store');

// show a specific user
Route::get('users/{user}', [UserController::class, 'show'])
->name('users.show');

// show edit form for a user populated with user data
Route::get('users/{user}/edit', [UsersController::class, 'edit'])
->name('users.edit');

// update a user in the database
Route::put('users/{user}', [UserController::class, 'update'])
->name('users.update');

// delete a user from the database
Route::delete('users/{user}', [UserController::class, 'destroy'])
->name('users.destroy');

The post routes for a specific user will look something like this:

// get all posts for a user
Route::get('users/{user}/posts', [PostController::class, 'index'])
->name('users.posts.index');

// show form to create a new post for a user
Route::get('users/{user}/posts/create', [PostController::class, 'create'])
->name('users.posts.create');

// create a new post for a user in the database

Route::post('users/{user}/posts', [PostController::class, 'store'])
->name('users.posts.store');

// show a specific post for a user
Route::get('users/{user}/posts/{post}', [PostController::class, 'show'])
->name('users.posts.show');

// show edit form for a post for a user populated with post data
Route::get('users/{user}/posts/{post}/edit', [PostController::class, 'edit'])
->name('users.posts.edit');

// update a post for a user in the database
Route::put('users/{user}/posts/{post}', [PostController::class, 'update'])
->name('users.posts.update');

// delete a post for a user from the database
Route::delete('users/{user}/posts/{post}', [PostController::class, 'destroy'])
->name('users.posts.destroy');

If we set up routes this way and have a posts relationship defined on the User model like this:

// User model
// ...
public function posts()
{
return $this->hasMany(Post::class);
}
// ...

Then we can use the posts relationship and route model binding to get the PostController method to work like this:

// PostsController

// ...
// create a new post for a user in the database
public function store(User $user)
{
$user->posts()->create($this->validatePost());
return redirect()->route('users.posts.index', $user);
}
// ...

Laravel’s conventions make the code more expressive and declarative while also saving us from writing extra code.

Named routes

Use plural form of the word and append the action name; e.g; users.index using dot notation in b/w.

// users.index named route
Route::get('users', [UserController::class, 'index'])
->name('users.index');

For multi-word Model/action use kebab-case; e.g; new-car.refuel-and-ignite.

// new-car.refuel-and-ignite named route
Route::post('new-car/refuel-and-ignite', [NewCarController::class, 'refuelAndIgnite'])
->name('new-car.refuel-and-ignite');

Views

Directory structure

Use plural form of the word and all lowercase. Example: users, posts , comments, tags. For multi-word Model/action use kebab-case;

File name Use index.blade.php for listing all records, create.blade.php for creating a new record, show.blade.php for showing a specific record, edit.blade.php for editing a specific record.

// UserController

// ...
// show all users
public function index()
{
$users = User::all();
// this is expressive and declarative and more aligned with the
// naming conventions we've been following
return view('users.index', ['users' => $users]);
}
// ...

Relationships

Use singular/plural form w.r.t relationship and use snake_case for multi-word Model names.

// single-word has many relationship
public function posts()
{
return $this->hasMany(Post::class);
}

// multi-word has many relationship
public function user_posts()
{
return $this->hasMany(UserPost::class);
}

// loading single-word has many relationship
$users = User::with('posts')->get();
// loading multi-word has many relationship
$users = User::with('user_posts')->get();

When we defined the relationships with these naming conventions; they are fascinating and more aligned with dot (.) notation.

If we have a tags relationship defined on the Post; we can load it like this:

// load all users with their posts and tags
$users = User::with('posts.tags')->get();

Miscellaneous

For interfaces, you can use directory app/Contracts.

For implementing the interface, you can use directory app/Actions.

For traits, you can use directory app/Concerns.

For others artisan commands is there to help you put things in their right place.

Conclusion

I hope you find this article helpful. If you have any questions or suggestions, please feel free to comment below.

--

--

Responses (1)