Database
Column Naming
- Primary key should be always
id
- Foreign keys should be a singular with the suffix
_id
(user_id
,product_id
,category_id
...) - When choosing a name you should be careful with reserved words
Recommended names for frequently used columns:
These names are recommendations on how to name certain columns to unify them across projects. These columns may be called differently, but it is good to keep them unified.
- order_column - field for sorting entities (e.g. drag & drop)
- company_number - field for CIN / Company Identification Number
- vat_number - field for VATIN / VAT ID / VAT Identification Number
- zip_code - field for ZIP Code / Postal Code / Post Code
Names by column type:
Recommended prefixes/suffixes for naming columns.
-
boolean -
is_enabled
,has_reviews
-
timestamp -
published_at
,valid_from
,scheduled_for
Pro tip: Some of boolean columns could be converted to timestamps (e.g. from
is_finished
tofinished_at
). There is a benefit that you have in fact two pieces of information in one column.
Column Sorting
This sorting helps for better reading and orientation in the database.
- id - should be always first
- foreign keys
- rest of columns - should be sorted by priorty and grouped by context
-
native timestamps -
created_at
,updated_at
UUIDs
Secure alternative to ids. They should be used for publicly accessible websites or for some tables with sensitive content (bank accounts, orders, invoices...). How to use UUIDs?
Indexes
Indexes are database structures that allow for faster retrieval of data. They work by creating a separate data structure that points to the location of the data in the database. When a query is run, the database can use the index to quickly find the relevant data instead of having to search the entire database.
Pro tip: Just use indexes on any columns that are frequently queried or used in joins and your database performance will be improved.
Migrations
Migrations are a way to version control your database schema. They allow you to make changes to the database schema in a structured way, and enable you to easily roll back changes if necessary. Read more
Best practices
- During development, it's fine to modify existing migration files and run
php artisan migrate:fresh
to reset the database - Once the project is in production, you have to create new migrations for every change, even small ones
- Don't break column sorting - when adding a new column use also
after()
andbefore()
methods - Add short note with
comment()
method if column names are not clear (e.g. professional terms)
Pro tip: As you build your application, you may accumulate more and more migrations over time. This can lead to your
database/migrations
directory becoming bloated with potentially hundreds of migrations. If you would like, you may "squash" your migrations into a single SQL file. Read more
Seeders
Seeders are used to populate a database with initial/testing data. They are especially useful in scenarios where you need to populate your database with pre-existing data, such as test data or reference data.
By using a seeder, you can quickly populate your database with the necessary data without having to manually enter it every time. Read more
-
Create command:
php artisan make:seeder UserSeeder
-
Running all seeders:
php artisan db:seed
-
Running specific seeder:
php artisan db:seed --class=UserSeeder
Pro tip: Sometimes you need to disable model events inside seeders. You can simply make it with a trait
WithoutModelEvents
. Read more
Naming
Singular with "Seeder" suffix (UserSeeder
, ProductSeeder
, CategorySeeder
...)
Factories
Laravel factories are a convenient way to generate fake data for testing purposes. They allow you to quickly and easily create instances of your models with randomized or custom attributes.
By defining a factory for each of your models, you can easily generate the data you need to test your application's functionality.
They are mostly used in seeders and tests. Read more
Create command: php artisan make:factory UserFactory
1class ProductFactory extends Factory 2{ 3 public function definition(): array 4 { 5 return [ 6 'name' => fake()->name(), 7 'category_id' => Category::factory(), 8 ... 9 ];10 }11}
Naming
Singular model name with "Factory" suffix (UserFactory
, ProductFactory
, CategoryFactory
...)
Usage Examples
Creating a single database record:
1User::factory()->create();
Creating a single model instance:
1User::factory()->make();
Creating multiple database records:
1User::factory()2 ->count(10)3 ->create();
Creating multiple model instances:
1User::factory()2 ->count(10)3 ->make();
Creating a factory with custom attributes:
1User::factory()->create(['email' => 'info@rockero.cz']);
Factory methods
Sequence
You can create models with more data variation. The sequence is looped until the desired number of models is reached. Read more
1// Creates 5 orders with state new and 5 orders with state pending:2Order::factory()3 ->count(10)4 ->sequence(5 ['state' => 'new'],6 ['state' => 'pending']7 )8 ->create();
You can use sequence also with callback:
1Category::factory()2 ->count(10)3 ->sequence(fn (Sequence $sequence) => ['order_column' => $sequence->index])4 ->create();
Recycle
Sometimes happens that nested factories have the same relationship as the base factory. For that, you may use the recycle()
method. It will re-use the relationship instance. Read more
1$tenant = Tenant::factory()->create(); 2 3// Bad example: 4Product::factory()->create(['tenant_id' => $tenant]); 5ProductCategory::factory()->create(['tenant_id' => $tenant]); 6 7// Good example: 8Product::factory() 9 ->recycle($tenant)10 ->has(ProductCategory::factory())11 ->create();
Relationships
1// Creates user with 3 posts: 2User::factory() 3 ->has(Post::factory()->count(3)) 4 ->create(); 5 6// Or using magic methods: 7User::factory() 8 ->hasPosts(3) 9 ->create();10 11// Creates post that belongs to the user:12Post::factory()13 ->count(3)14 ->for(User::factory(['name' => 'Rockero']))15 ->create();16 17// Or using magic methods:18Post::factory()19 ->count(3)20 ->forUser(['name' => 'Rockero'])21 ->create();22 23// Creates user with 3 roles and custom pivot value:24User::factory()25 ->hasAttached(26 Role::factory()->count(3),27 ['active' => true]28 )29 ->create();30 31// Or using magic methods:32User::factory()33 ->hasRoles(1, ['name' => 'Editor'])34 ->create();
Custom Methods
Instead of creating client with following setup in each test:
1Client::factory([2 'platform' => 'github',3 'email' => 'info@rockero.cz'4])->create();
you can create a custom method in ClientFactory
:
1public function github(array $attributes = []): self2{3 return $this->state([4 'platform' => 'github',5 'email' => $attributes['email'] ?? fake()->email()6 'access_token' => $attributes['token'] ?? Str::random()7 ]);8}
and then use it like that:
1Client::factory()2 ->github()3 ->create();
Factory Callbacks
Factory callbacks can be used to perform an action after making or creating a model.
These methods are defined inside configure()
method on factory class.
1public function configure(): static2{3 return $this->afterMaking(function (User $user) {4 // ...5 })->afterCreating(function (User $user) {6 // ...7 });8}