Skip to content

Práce s databází

Projekt používá MariaDB 11 v Docker prostředí a Laravel migrations pro správu databázového schématu.

V .env:

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=kviz
DB_USERNAME=kviz
DB_PASSWORD=kviz

V phpunit.xml:

<env name="DB_CONNECTION" value="mysql"/>
<env name="DB_HOST" value="db"/>
<env name="DB_DATABASE" value="kviz_testing"/>

Terminal window
# Spustit všechny pending migrace
docker compose exec fpm php artisan migrate
# S seedováním
docker compose exec fpm php artisan migrate --seed
# Rollback poslední dávky
docker compose exec fpm php artisan migrate:rollback
# Rollback všech migrací
docker compose exec fpm php artisan migrate:reset
# Reset a opětovné spuštění všech migrací
docker compose exec fpm php artisan migrate:fresh
# Fresh s seedováním
docker compose exec fpm php artisan migrate:fresh --seed
Terminal window
# Vytvořit novou tabulku
docker compose exec fpm php artisan make:migration create_products_table
# Přidat sloupec do existující tabulky
docker compose exec fpm php artisan make:migration add_status_to_products_table
# S automatickým generováním struktury
docker compose exec fpm php artisan make:migration create_products_table --create=products
docker compose exec fpm php artisan make:migration add_status_to_products --table=products
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('chk_products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->foreignId('category_id')
->constrained('chk_categories')
->onDelete('cascade');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('chk_products');
}
};
// ✅ Správně
Schema::create('chk_products', function (Blueprint $table) { ... });
// ❌ Špatně
Schema::create('products', function (Blueprint $table) { ... });
$table->foreignId('region_id')
->constrained('chk_regions')
->onDelete('set null')
->onUpdate('cascade');

3. Používejte nullable() pro volitelná pole

Section titled “3. Používejte nullable() pro volitelná pole”
$table->string('phone')->nullable();
$table->text('description')->nullable();

4. Indexujte časté vyhledávací sloupce

Section titled “4. Indexujte časté vyhledávací sloupce”
$table->string('email')->unique();
$table->index('status');
$table->index(['user_id', 'created_at']);

Seedery slouží k naplnění databáze testovacími daty.

database/seeders/
├── DatabaseSeeder.php # Hlavní seeder
├── CountrySeeder.php # Základní data - země
├── RegionSeeder.php # Základní data - kraje
├── CitySeeder.php # Základní data - města
├── UserRoleCompanySeeder.php # Testovací uživatelé
└── PubSeeder.php # Testovací puby
Terminal window
# Všechny seedery (DatabaseSeeder)
docker compose exec fpm php artisan db:seed
# Konkrétní seeder
docker compose exec fpm php artisan db:seed --class=CountrySeeder
# Fresh migrate + seed
docker compose exec fpm php artisan migrate:fresh --seed
Terminal window
docker compose exec fpm php artisan make:seeder ProductSeeder
<?php
namespace Database\Seeders;
use App\Models\Product;
use Illuminate\Database\Seeder;
class ProductSeeder extends Seeder
{
public function run(): void
{
$products = [
['name' => 'Product 1', 'price' => 100],
['name' => 'Product 2', 'price' => 200],
];
foreach ($products as $product) {
Product::updateOrCreate(
['name' => $product['name']], // Unique identifikátor
$product // Data k aktualizaci
);
}
$this->command?->info("✅ ProductSeeder dokončen. Vytvořeno/aktualizováno " . count($products) . " produktů.");
}
}
public function run()
{
if (app()->environment('production')) {
$this->command?->warn('⚠️ Seeding je zakázáno v produkčním prostředí.');
return;
}
// Základní data v pořadí závislostí
$this->call([
CountrySeeder::class, // 1. Země (bez závislostí)
RegionSeeder::class, // 2. Regiony (vyžadují Country)
CitySeeder::class, // 3. Města (vyžadují Region)
]);
// Testovací data
$this->call([
UserRoleCompanySeeder::class,
PubSeeder::class, // 4. Puby (vyžadují City)
]);
}

Factories generují testovací data pro modely.

Terminal window
docker compose exec fpm php artisan make:factory ProductFactory --model=Product
<?php
namespace Database\Factories;
use App\Models\Product;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProductFactory extends Factory
{
protected $model = Product::class;
public function definition(): array
{
return [
'name' => $this->faker->words(3, true),
'description' => $this->faker->sentence(),
'price' => $this->faker->randomFloat(2, 10, 1000),
'category_id' => 1, // Nebo Category::factory()
];
}
/**
* State pro drahé produkty
*/
public function expensive(): static
{
return $this->state(fn (array $attributes) => [
'price' => $this->faker->randomFloat(2, 1000, 10000),
]);
}
/**
* State pro vyprodané produkty
*/
public function soldOut(): static
{
return $this->state(fn (array $attributes) => [
'stock' => 0,
]);
}
}
<?php
namespace App\Models;
use Database\Factories\ProductFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected static function newFactory()
{
return ProductFactory::new();
}
// ... rest of model
}
// Vytvořit jeden produkt (bez uložení)
$product = Product::factory()->make();
// Vytvořit a uložit jeden produkt
$product = Product::factory()->create();
// Vytvořit 10 produktů
$products = Product::factory()->count(10)->create();
// S vlastními atributy
$product = Product::factory()->create([
'name' => 'Custom Product',
'price' => 99.99,
]);
// Použití states
$expensiveProduct = Product::factory()->expensive()->create();
$soldOutProduct = Product::factory()->soldOut()->create();
// S relacemi
$product = Product::factory()
->for(Category::factory())
->create();

Terminal window
# Export do SQL souboru
docker compose exec db mysqldump -u kviz -pkviz kviz > database/schema/mysql-schema.sql
# Export pouze struktury (bez dat)
docker compose exec db mysqldump -u kviz -pkviz --no-data kviz > database/schema/mysql-schema.sql
Terminal window
# Import do existující databáze
docker compose exec -T db mysql -u kviz -pkviz kviz < database/schema/mysql-schema.sql
# Import do testovací databáze
docker compose exec -T db mysql -u kviz -pkviz kviz_testing < database/schema/mysql-schema.sql

Pokud máte příliš mnoho migrací, můžete je “squashnout” do jednoho SQL souboru.

Terminal window
docker compose exec db mysqldump -u kviz -pkviz --no-data kviz > database/schema/mysql-schema-YYYY-MM-DD.sql
Terminal window
mkdir database/migrations/archived
mv database/migrations/2024_*.php database/migrations/archived/
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
$sql = file_get_contents(database_path('schema/mysql-schema-2024-12-01.sql'));
DB::unprepared($sql);
}
public function down(): void
{
// Rollback není možný pro squash migraci
throw new Exception('Cannot rollback squashed migrations');
}
};

Terminal window
# Kompletní reset
docker compose exec fpm php artisan migrate:fresh --seed
# Pouze migrace (bez seedování)
docker compose exec fpm php artisan migrate:fresh
Terminal window
docker compose exec fpm php artisan migrate:status
Terminal window
# MySQL CLI
docker compose exec db mysql -u kviz -pkviz kviz
# Nebo přes fpm kontejner
docker compose exec fpm php artisan db
# Tinker (Laravel REPL)
docker compose exec fpm php artisan tinker
Terminal window
# Automatický backup script
./backup-database.sh
# Manuální backup
docker compose exec db mysqldump -u kviz -pkviz kviz > backup-$(date +%Y%m%d-%H%M%S).sql
Terminal window
docker compose exec -T db mysql -u kviz -pkviz kviz < backup-20240101-120000.sql

  • Tabulky: Plural, snake_case s prefixem chk_

    • chk_users, chk_regions, chk_quiz_events
  • Sloupce: snake_case

    • user_id, created_at, display_name
  • Foreign keys: {model}_id

    • country_id, region_id, user_id
  • Pivot tabulky: Alphabetically ordered, singular, s prefixem

    • chk_pub_user, chk_quiz_team
  • Indexy: {table}_{column}_index

    • users_email_index, regions_country_id_index
$table->id(); // BIGINT UNSIGNED AUTO_INCREMENT
$table->string('name', 255); // VARCHAR(255)
$table->text('description'); // TEXT
$table->integer('count'); // INT
$table->decimal('price', 10, 2); // DECIMAL(10,2)
$table->boolean('active'); // TINYINT(1)
$table->timestamp('published_at'); // TIMESTAMP
$table->timestamps(); // created_at, updated_at
$table->softDeletes(); // deleted_at

Terminal window
# Spusťte migrace
docker compose exec fpm php artisan migrate
Terminal window
# Zkontrolujte credentials v .env
# Zkontrolujte, že databázový kontejner běží
docker compose ps db
Terminal window
# Zkontrolujte syntax v migraci
# Zkontrolujte existenci foreign key tabulek
# Rollback a zkuste znovu
docker compose exec fpm php artisan migrate:rollback
docker compose exec fpm php artisan migrate