If you've used Laravel even briefly, you've noticed something unusual. You write surprisingly little configuration code. You don't tell Laravel where your models are, what your table names are, or how your foreign keys connect — it just figures it out. This isn't magic. It's a deliberate design philosophy called Convention Over Configuration, and once you understand it deeply, the entire framework starts making sense in a new way.
What Does Convention Over Configuration Actually Mean
Most frameworks require you to explicitly configure everything. You tell the system where your files are, what your tables are named, how relationships connect. You write configuration before you write logic.
Convention over configuration flips this. The framework decides a set of standard rules upfront — conventions. As long as you follow those conventions, zero configuration is needed. You only write configuration when you deliberately want to break from the standard.
Laravel borrowed this philosophy from Ruby on Rails, which popularized it in 2004. Laravel applies it across nearly every layer of the framework.
Convention 1 — Models and Table Names
Create a model called Post. Laravel automatically assumes the database table is called posts.
php artisan make:model Post
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model {
}
That's the entire model. No table name specified. Laravel applies the convention:
Model name → Lowercase → Plural → Table name
Post → post → posts → posts table
More examples:
|
Model Name |
Assumed
Table |
|
User |
users |
|
BlogPost |
blog_posts |
|
OrderItem |
order_items |
|
Category |
categories |
|
Person |
people |
Laravel uses proper English pluralization — it knows Person becomes people, not persons.
Breaking the Convention
If your table is named differently for any reason, just declare it explicitly:
class Post extends Model { protected $table = 'articles'; }
Now Laravel uses articles instead of posts. Convention ignored for this model only.
Convention 2 — Primary Keys
Every table is assumed to have a primary key column named id of type bigint unsigned with auto increment.
Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->timestamps(); });
$table->id() creates a column named id — and Laravel's Eloquent automatically knows this is the primary key without you specifying anything.
Breaking the Convention
Now Laravel uses post_id as the primary key, treats it as non-incrementing, and expects a string type like a UUID.Convention 3 — Foreign Keys and constrained()
This is where the convention becomes most visually impressive. When you define a foreign key column, the name you give it tells Laravel everything:
$table->foreignId('user_id')->constrained();
Laravel reads user_id and applies the convention:
Column name → Strip_id → Plural → Table name → id column
user_id → user → users → users.id
So constrained() with no arguments automatically creates a foreign key constraint pointing to users.id. You wrote one word. Laravel figured out the entire relationship.
Schema::create('posts', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); $table->foreignId('category_id')->constrained(); $table->string('title'); $table->timestamps(); });
Two foreign keys, zero explicit table references. Laravel derived both:
user_id→users.idcategory_id→categories.id
Breaking the Convention
When your column name doesn't follow the pattern, tell Laravel explicitly:
$table->foreignId('author_id')->constrained('users'); $table->foreignId('approved_by')->constrained('users', 'id'); $table->foreignId('parent_post_id')->constrained('posts');
author_id would make Laravel look for an authors table that doesn't exist. Passing 'users' overrides that assumption.
Convention 4 — Timestamps
Every migration you generate includes this line by default:
$table->timestamps();
This creates two columns — created_at and updated_at. Eloquent automatically manages both. When you create a record, created_at is set. When you update it, updated_at is updated. You never touch these columns manually.
$post = Post::create(['title' => 'My First Post']);
echo $post->created_at; echo $post->updated_at;
Both are populated automatically because Laravel follows the convention that these two columns exist and have these exact names.
Breaking the Convention
class Post extends Model { public $timestamps = false; }
Or use custom column names:
class Post extends Model { const CREATED_AT = 'created_date'; const UPDATED_AT = 'modified_date'; }
Convention 5 — Eloquent Relationships
Relationships follow the same naming logic. When you define a belongsTo relationship:
class Post extends Model { public function user() { return $this->belongsTo(User::class); } }
Laravel assumes the foreign key on the posts table is user_id — derived from the User model name. You don't pass the key name. The convention handles it.
class User extends Model { public function posts() { return $this->hasMany(Post::class); } }
hasMany assumes the foreign key on posts is user_id — derived from the current model name User.
Full Relationship Example
class Comment extends Model { public function post() { return $this->belongsTo(Post::class); }
public function user() { return $this->belongsTo(User::class); } }
Laravel assumes:
commentstable haspost_id→ referencesposts.idcommentstable hasuser_id→ referencesusers.id
Zero configuration. Just model names and Laravel fills in the rest.
Breaking the Convention
public function author() { return $this->belongsTo(User::class, 'author_id', 'id'); }
Method is named author, but the actual foreign key is author_id and it points to User. Laravel can't derive this from the method name alone, so you pass the key explicitly.
Convention 6 — Controllers and Resource Naming
When you generate a resource controller:
php artisan make:controller PostController --resource
Laravel generates seven methods following REST conventions:
|
Method |
Route |
HTTP Verb |
Purpose |
|
index |
/posts |
GET |
List all
posts |
|
create |
/posts/create |
GET |
Show create
form |
|
store |
/posts |
POST |
Save new post |
|
show |
/posts/{post} |
GET |
Show one post |
|
edit |
/posts/{post}/edit |
GET |
Show edit
form |
|
update |
/posts/{post} |
PUT/PATCH |
Update post |
|
destroy |
/posts/{post} |
DELETE |
Delete post |
Register all seven with one line:
Route::resource('posts', PostController::class);
The naming convention between controller method names and HTTP verbs is baked in. Laravel knows store means POST, destroy means DELETE, without you mapping anything.
Convention 7 — View Files and the view() Helper
return view('posts.index');
Laravel looks for this file at:
resources/views/posts/index.blade.php
The dot notation maps directly to folder structure. posts.index means posts folder, index file, .blade.php extension assumed automatically.
return view('admin.posts.edit'); // resources/views/admin/posts/edit.blade.php
return view('emails.welcome'); // resources/views/emails/welcome.blade.php
No path configuration, no extension specification. Convention handles it.
Convention 8 — Route Model Binding
This is one of the most elegant conventions in Laravel. Define a route with a parameter that matches a model name:
Route::get('/posts/{post}', [PostController::class, 'show']);
Then type-hint it in your controller:
public function show(Post $post) { return view('posts.show', compact('post')); }
Laravel sees that the parameter is named post and the type-hint is Post model. It automatically queries the database for Post::find($post) and injects the model instance. If the record doesn't exist, it returns a 404 automatically.
No manual Post::find($id) call. No abort(404) check. Convention does it all.
The Full Convention Map
|
What
Laravel Derives |
From What |
|
Table name
(posts) |
Model name
(Post) |
|
Primary key
(id) |
Default
assumption |
|
Foreign key
(user_id) |
belongsTo(User::class) |
|
Referenced
table (users) |
Column name
(user_id) |
|
Timestamps
management |
created_at / updated_at columns |
|
View file
path |
Dot notation
string |
|
Route model |
Parameter
name + type-hint |
|
Resource
routes |
Controller
method names |
Why This Philosophy Matters
Every convention Laravel follows removes a decision you have to make. In a large application with dozens of models, controllers, and relationships, those removed decisions add up to thousands of lines of configuration you never had to write.
More importantly, conventions create consistency across teams. A new developer joining a Laravel project already knows where models are, what tables are named, how foreign keys are structured — because every Laravel project follows the same rules. The codebase is predictable before they read a single line.
The configuration option always exists when you need it. But the goal is to need it as rarely as possible.
The Bottom Line
Convention over configuration is not a shortcut or a limitation. It's a contract between you and the framework. Laravel promises to handle all the boilerplate as long as you follow its naming rules. In return, you write less code, make fewer decisions, and maintain more consistent codebases. Understanding the conventions deeply — not just following them blindly — is what separates a developer who uses Laravel from a developer who understands it.
No comments:
Post a Comment