Start MySQL implementation: config template, PDO layer and migrations
This commit is contained in:
parent
e22476da57
commit
883ff30877
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,6 +15,7 @@ Thumbs.db
|
|||
|
||||
# Local secrets
|
||||
deploy-config.php
|
||||
config.php
|
||||
|
||||
# Logs/temp
|
||||
*.log
|
||||
|
|
|
|||
21
README.md
21
README.md
|
|
@ -55,6 +55,27 @@ photo-gallery/
|
|||
php -S 127.0.0.1:8080
|
||||
```
|
||||
|
||||
## MySQL конфиг и миграции (этап перехода на БД)
|
||||
|
||||
1. Создай локальный конфиг из шаблона:
|
||||
|
||||
```bash
|
||||
cp config.php.example config.php
|
||||
```
|
||||
|
||||
2. Заполни доступы к MySQL в `config.php`.
|
||||
|
||||
3. Прогони миграции:
|
||||
|
||||
```bash
|
||||
php scripts/migrate.php
|
||||
```
|
||||
|
||||
Файлы:
|
||||
- `lib/db.php` — подключение PDO
|
||||
- `migrations/*.sql` — схема БД
|
||||
- `scripts/migrate.php` — runner миграций
|
||||
|
||||
Открыть в браузере:
|
||||
|
||||
- `http://127.0.0.1:8080`
|
||||
|
|
|
|||
11
config.php.example
Normal file
11
config.php.example
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
return [
|
||||
'db' => [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 3306,
|
||||
'name' => 'photo_gallery',
|
||||
'user' => 'gallery_user',
|
||||
'pass' => 'change_me',
|
||||
'charset' => 'utf8mb4',
|
||||
],
|
||||
];
|
||||
47
lib/db.php
Normal file
47
lib/db.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
function appConfig(): array
|
||||
{
|
||||
static $cfg = null;
|
||||
if ($cfg !== null) {
|
||||
return $cfg;
|
||||
}
|
||||
|
||||
$path = __DIR__ . '/../config.php';
|
||||
if (!is_file($path)) {
|
||||
throw new RuntimeException('config.php not found. Copy config.php.example');
|
||||
}
|
||||
|
||||
$cfg = require $path;
|
||||
if (!is_array($cfg)) {
|
||||
throw new RuntimeException('Invalid config.php format');
|
||||
}
|
||||
|
||||
return $cfg;
|
||||
}
|
||||
|
||||
function db(): PDO
|
||||
{
|
||||
static $pdo = null;
|
||||
if ($pdo instanceof PDO) {
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
$db = appConfig()['db'] ?? [];
|
||||
$host = $db['host'] ?? '127.0.0.1';
|
||||
$port = (int)($db['port'] ?? 3306);
|
||||
$name = $db['name'] ?? '';
|
||||
$user = $db['user'] ?? '';
|
||||
$pass = $db['pass'] ?? '';
|
||||
$charset = $db['charset'] ?? 'utf8mb4';
|
||||
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$name};charset={$charset}";
|
||||
$pdo = new PDO($dsn, (string)$user, (string)$pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
54
migrations/001_init.sql
Normal file
54
migrations/001_init.sql
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
CREATE TABLE IF NOT EXISTS sections (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 1000,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_sections_name (name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS photos (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
section_id BIGINT UNSIGNED NOT NULL,
|
||||
code_name VARCHAR(191) NOT NULL,
|
||||
description TEXT NULL,
|
||||
sort_order INT NOT NULL DEFAULT 1000,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_photos_section FOREIGN KEY (section_id) REFERENCES sections(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uq_photos_code_name (code_name),
|
||||
KEY idx_photos_section_sort (section_id, sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS photo_files (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
photo_id BIGINT UNSIGNED NOT NULL,
|
||||
kind ENUM('before','after') NOT NULL,
|
||||
file_path VARCHAR(500) NOT NULL,
|
||||
mime_type VARCHAR(100) NOT NULL,
|
||||
size_bytes INT UNSIGNED NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_photo_files_photo FOREIGN KEY (photo_id) REFERENCES photos(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uq_photo_files_kind (photo_id, kind)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS comment_users (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
display_name VARCHAR(191) NOT NULL,
|
||||
token_hash CHAR(64) NOT NULL,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_comment_users_token_hash (token_hash)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS photo_comments (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
photo_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NULL,
|
||||
comment_text TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_photo_comments_photo FOREIGN KEY (photo_id) REFERENCES photos(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_photo_comments_user FOREIGN KEY (user_id) REFERENCES comment_users(id) ON DELETE SET NULL,
|
||||
KEY idx_photo_comments_photo_created (photo_id, created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
48
scripts/migrate.php
Executable file
48
scripts/migrate.php
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../lib/db.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
} catch (Throwable $e) {
|
||||
fwrite(STDERR, "DB connection failed: " . $e->getMessage() . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$pdo->exec('CREATE TABLE IF NOT EXISTS migrations (name VARCHAR(191) PRIMARY KEY, applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');
|
||||
|
||||
$files = glob(__DIR__ . '/../migrations/*.sql') ?: [];
|
||||
sort($files, SORT_NATURAL);
|
||||
|
||||
$check = $pdo->prepare('SELECT 1 FROM migrations WHERE name = :name');
|
||||
$mark = $pdo->prepare('INSERT INTO migrations(name) VALUES (:name)');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$name = basename($file);
|
||||
$check->execute(['name' => $name]);
|
||||
if ($check->fetchColumn()) {
|
||||
echo "skip {$name}" . PHP_EOL;
|
||||
continue;
|
||||
}
|
||||
|
||||
echo "apply {$name}" . PHP_EOL;
|
||||
$sql = file_get_contents($file);
|
||||
if ($sql === false) {
|
||||
throw new RuntimeException("Cannot read {$file}");
|
||||
}
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$pdo->exec($sql);
|
||||
$mark->execute(['name' => $name]);
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
echo "done" . PHP_EOL;
|
||||
Loading…
Reference in New Issue
Block a user