Initial commit: RSS Hub for agents with migration system
This commit is contained in:
commit
5a6e32758f
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Database Configuration
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_NAME=rss_hub
|
||||||
|
DB_USER=rss_hub_user
|
||||||
|
DB_PASS=secure_password
|
||||||
|
|
||||||
|
# Application Settings
|
||||||
|
APP_ENV=development
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://localhost:8080
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
# RSS Hub for Agents
|
||||||
|
|
||||||
|
Open-source hub for RSS feeds registration and discovery by agents.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Register RSS/Atom feeds with metadata
|
||||||
|
- Categorize and tag feeds
|
||||||
|
- Track feed statistics
|
||||||
|
- Simple API for agents to register and discover feeds
|
||||||
|
- Terminal-style HTML interface
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- PHP 8.1+
|
||||||
|
- MySQL/MariaDB
|
||||||
|
- Composer
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Install dependencies: `composer install`
|
||||||
|
3. Configure database in `config/database.php`
|
||||||
|
4. Run migrations: `php app.php migrate`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Migrations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply all pending migrations
|
||||||
|
php app.php migrate
|
||||||
|
|
||||||
|
# Rollback last migration
|
||||||
|
php app.php rollback
|
||||||
|
|
||||||
|
# Check migration status
|
||||||
|
php app.php status
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Загрузка автозагрузчика Composer (если есть)
|
||||||
|
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
} else {
|
||||||
|
// Простая реализация автозагрузки для нашего случая
|
||||||
|
spl_autoload_register(function ($class) {
|
||||||
|
$prefix = 'App\\';
|
||||||
|
$len = strlen($prefix);
|
||||||
|
|
||||||
|
if (strncmp($prefix, $class, $len) !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativeClass = substr($class, $len);
|
||||||
|
$file = __DIR__ . '/src/' . str_replace('\\', '/', $relativeClass) . '.php';
|
||||||
|
|
||||||
|
if (file_exists($file)) {
|
||||||
|
require $file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
use App\MigrationRunner;
|
||||||
|
|
||||||
|
// Загрузка конфигурации БД
|
||||||
|
$config = require_once __DIR__ . '/config/database.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dsn = "mysql:host={$config['host']};dbname={$config['database']};charset=utf8mb4";
|
||||||
|
$pdo = new PDO($dsn, $config['username'], $config['password'], [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
]);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Ошибка подключения к БД: " . $e->getMessage() . "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$migrationRunner = new MigrationRunner($pdo);
|
||||||
|
|
||||||
|
// Обработка аргументов командной строки
|
||||||
|
$command = $argv[1] ?? null;
|
||||||
|
|
||||||
|
switch ($command) {
|
||||||
|
case 'migrate':
|
||||||
|
echo "Запуск миграций...\n";
|
||||||
|
$migrationRunner->migrate();
|
||||||
|
echo "Миграции завершены!\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rollback':
|
||||||
|
echo "Откат последней миграции...\n";
|
||||||
|
$migrationRunner->rollback();
|
||||||
|
echo "Откат завершен!\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
echo "Проверка статуса миграций...\n";
|
||||||
|
$stmt = $pdo->query("SELECT migration_name, applied_at FROM migrations ORDER BY applied_at ASC");
|
||||||
|
$appliedMigrations = $stmt->fetchAll();
|
||||||
|
|
||||||
|
if (empty($appliedMigrations)) {
|
||||||
|
echo "Нет примененных миграций.\n";
|
||||||
|
} else {
|
||||||
|
echo "Примененные миграции:\n";
|
||||||
|
foreach ($appliedMigrations as $migration) {
|
||||||
|
echo "- {$migration['migration_name']} ({$migration['applied_at']})\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать непримененные миграции
|
||||||
|
$migrationFiles = glob(__DIR__ . '/migrations/*.php');
|
||||||
|
natsort($migrationFiles);
|
||||||
|
|
||||||
|
$appliedNames = array_column($appliedMigrations, 'migration_name');
|
||||||
|
$unapplied = [];
|
||||||
|
|
||||||
|
foreach ($migrationFiles as $file) {
|
||||||
|
$name = basename($file, '.php');
|
||||||
|
if (!in_array($name, $appliedNames)) {
|
||||||
|
$unapplied[] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($unapplied)) {
|
||||||
|
echo "\nНепримененные миграции:\n";
|
||||||
|
foreach ($unapplied as $migration) {
|
||||||
|
echo "- $migration\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
echo "Использование: php app.php [migrate|rollback|status]\n";
|
||||||
|
echo " migrate - применить все непримененные миграции\n";
|
||||||
|
echo " rollback - откатить последнюю миграцию\n";
|
||||||
|
echo " status - показать статус миграций\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "rss-hub/rss-hub",
|
||||||
|
"description": "Open-source RSS hub for agents",
|
||||||
|
"type": "project",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "RSS Hub Team",
|
||||||
|
"email": "info@rss-hub.org"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"slim/slim": "^4.12",
|
||||||
|
"slim/psr7": "^1.6",
|
||||||
|
"nyholm/psr7": "^1.8",
|
||||||
|
"fig/http-message-util": "^1.1",
|
||||||
|
"vlucas/phpdotenv": "^5.5",
|
||||||
|
"ramsey/uuid": "^4.7"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^10.0",
|
||||||
|
"squizlabs/php_codesniffer": "^3.7"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"post-install-cmd": [
|
||||||
|
"chmod +x app.php"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"chmod +x app.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"sort-packages": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Конфигурация базы данных для RSS Hub
|
||||||
|
|
||||||
|
return [
|
||||||
|
'host' => $_ENV['DB_HOST'] ?? 'localhost',
|
||||||
|
'database' => $_ENV['DB_NAME'] ?? 'rss_hub',
|
||||||
|
'username' => $_ENV['DB_USER'] ?? 'rss_hub_user',
|
||||||
|
'password' => $_ENV['DB_PASS'] ?? 'secure_password',
|
||||||
|
'charset' => 'utf8mb4',
|
||||||
|
'collation' => 'utf8mb4_unicode_ci',
|
||||||
|
'options' => [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../src/BaseMigration.php';
|
||||||
|
|
||||||
|
class CreateBaseTables extends \App\BaseMigration
|
||||||
|
{
|
||||||
|
public function up(\PDO $pdo): void
|
||||||
|
{
|
||||||
|
// Создание таблицы owners (агенты)
|
||||||
|
$ownersSql = "
|
||||||
|
CREATE TABLE owners (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
contact VARCHAR(255),
|
||||||
|
api_key VARCHAR(255) UNIQUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_activity TIMESTAMP,
|
||||||
|
status ENUM('active', 'suspended') DEFAULT 'active'
|
||||||
|
)
|
||||||
|
";
|
||||||
|
$pdo->exec($ownersSql);
|
||||||
|
|
||||||
|
// Создание таблицы categories
|
||||||
|
$categoriesSql = "
|
||||||
|
CREATE TABLE categories (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(100) UNIQUE NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
";
|
||||||
|
$pdo->exec($categoriesSql);
|
||||||
|
|
||||||
|
// Создание таблицы tags
|
||||||
|
$tagsSql = "
|
||||||
|
CREATE TABLE tags (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
";
|
||||||
|
$pdo->exec($tagsSql);
|
||||||
|
|
||||||
|
// Создание таблицы feeds
|
||||||
|
$feedsSql = "
|
||||||
|
CREATE TABLE feeds (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
url VARCHAR(255) NOT NULL,
|
||||||
|
title VARCHAR(255),
|
||||||
|
description TEXT,
|
||||||
|
category_id INT,
|
||||||
|
refresh_interval INT,
|
||||||
|
owner_id INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
status ENUM('active', 'inactive', 'suspended') DEFAULT 'active',
|
||||||
|
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL,
|
||||||
|
FOREIGN KEY (owner_id) REFERENCES owners(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
";
|
||||||
|
$pdo->exec($feedsSql);
|
||||||
|
|
||||||
|
// Создание таблицы feed_tags (many-to-many связь)
|
||||||
|
$feedTagsSql = "
|
||||||
|
CREATE TABLE feed_tags (
|
||||||
|
feed_id INT NOT NULL,
|
||||||
|
tag_id INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (feed_id, tag_id),
|
||||||
|
FOREIGN KEY (feed_id) REFERENCES feeds(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
";
|
||||||
|
$pdo->exec($feedTagsSql);
|
||||||
|
|
||||||
|
// Создание таблицы feed_stats (опционально)
|
||||||
|
$feedStatsSql = "
|
||||||
|
CREATE TABLE feed_stats (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
feed_id INT NOT NULL,
|
||||||
|
access_count INT DEFAULT 0,
|
||||||
|
last_access TIMESTAMP,
|
||||||
|
FOREIGN KEY (feed_id) REFERENCES feeds(id) ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
";
|
||||||
|
$pdo->exec($feedStatsSql);
|
||||||
|
|
||||||
|
// Создание индексов для производительности
|
||||||
|
$pdo->exec("CREATE INDEX idx_feeds_status ON feeds(status)");
|
||||||
|
$pdo->exec("CREATE INDEX idx_feeds_owner ON feeds(owner_id)");
|
||||||
|
$pdo->exec("CREATE INDEX idx_feeds_category ON feeds(category_id)");
|
||||||
|
$pdo->exec("CREATE INDEX idx_owners_api_key ON owners(api_key)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(\PDO $pdo): void
|
||||||
|
{
|
||||||
|
// Удаление таблиц в обратном порядке из-за внешних ключей
|
||||||
|
$pdo->exec("DROP TABLE IF EXISTS feed_stats");
|
||||||
|
$pdo->exec("DROP TABLE IF EXISTS feed_tags");
|
||||||
|
$pdo->exec("DROP TABLE IF EXISTS feeds");
|
||||||
|
$pdo->exec("DROP TABLE IF EXISTS tags");
|
||||||
|
$pdo->exec("DROP TABLE IF EXISTS categories");
|
||||||
|
$pdo->exec("DROP TABLE IF EXISTS owners");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../src/BaseMigration.php';
|
||||||
|
|
||||||
|
class AddDefaultCategories extends \App\BaseMigration
|
||||||
|
{
|
||||||
|
public function up(\PDO $pdo): void
|
||||||
|
{
|
||||||
|
// Добавляем базовые категории
|
||||||
|
$categories = [
|
||||||
|
['name' => 'tech', 'description' => 'Технические блоги и новости'],
|
||||||
|
['name' => 'news', 'description' => 'Новостные ленты'],
|
||||||
|
['name' => 'blog', 'description' => 'Персональные блоги'],
|
||||||
|
['name' => 'science', 'description' => 'Научные публикации'],
|
||||||
|
['name' => 'business', 'description' => 'Бизнес и экономика'],
|
||||||
|
['name' => 'art', 'description' => 'Искусство и культура'],
|
||||||
|
['name' => 'education', 'description' => 'Образование и обучение'],
|
||||||
|
['name' => 'development', 'description' => 'Разработка ПО'],
|
||||||
|
['name' => 'ai', 'description' => 'Искусственный интеллект'],
|
||||||
|
['name' => 'security', 'description' => 'Информационная безопасность']
|
||||||
|
];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO categories (name, description) VALUES (?, ?)");
|
||||||
|
|
||||||
|
foreach ($categories as $category) {
|
||||||
|
$stmt->execute([$category['name'], $category['description']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(\PDO $pdo): void
|
||||||
|
{
|
||||||
|
// Удаляем только добавленные категории (без ID, удаляем по именам)
|
||||||
|
$names = ['tech', 'news', 'blog', 'science', 'business', 'art', 'education', 'development', 'ai', 'security'];
|
||||||
|
$placeholders = str_repeat('?,', count($names) - 1) . '?';
|
||||||
|
|
||||||
|
$sql = "DELETE FROM categories WHERE name IN ($placeholders)";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
abstract class BaseMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Применить миграцию
|
||||||
|
*/
|
||||||
|
abstract public function up(\PDO $pdo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Откатить миграцию
|
||||||
|
*/
|
||||||
|
abstract public function down(\PDO $pdo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверить, была ли миграция уже применена
|
||||||
|
*/
|
||||||
|
public function isApplied(\PDO $pdo): bool
|
||||||
|
{
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM migrations
|
||||||
|
WHERE migration_name = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([basename(str_replace('.php', '', $this->getName()))]);
|
||||||
|
return $stmt->fetchColumn() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить имя миграции
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return static::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
use PDOException;
|
||||||
|
|
||||||
|
class MigrationRunner
|
||||||
|
{
|
||||||
|
private $pdo;
|
||||||
|
private $migrationsDir;
|
||||||
|
|
||||||
|
public function __construct(PDO $pdo, string $migrationsDir = '../migrations/')
|
||||||
|
{
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
$this->migrationsDir = $migrationsDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запустить все непримененные миграции
|
||||||
|
*/
|
||||||
|
public function migrate(): void
|
||||||
|
{
|
||||||
|
$this->ensureMigrationTableExists();
|
||||||
|
|
||||||
|
$appliedMigrations = $this->getAppliedMigrations();
|
||||||
|
$migrationFiles = $this->getMigrationFiles();
|
||||||
|
|
||||||
|
foreach ($migrationFiles as $filename) {
|
||||||
|
$migrationName = $this->getMigrationNameFromFilename($filename);
|
||||||
|
|
||||||
|
if (!in_array($migrationName, $appliedMigrations)) {
|
||||||
|
echo "Применяем миграцию: $migrationName\n";
|
||||||
|
|
||||||
|
$migrationClass = $this->loadMigrationClass($filename);
|
||||||
|
|
||||||
|
if ($migrationClass && method_exists($migrationClass, 'up')) {
|
||||||
|
$migrationClass->up($this->pdo);
|
||||||
|
$this->markAsApplied($migrationName);
|
||||||
|
echo "✓ Миграция $migrationName успешно применена\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ Ошибка: Метод up() не найден в миграции $migrationName\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Откатить последнюю миграцию
|
||||||
|
*/
|
||||||
|
public function rollback(): void
|
||||||
|
{
|
||||||
|
$lastAppliedMigration = $this->getLastAppliedMigration();
|
||||||
|
|
||||||
|
if (!$lastAppliedMigration) {
|
||||||
|
echo "Нет миграций для отката\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Откатываем миграцию: {$lastAppliedMigration['migration_name']}\n";
|
||||||
|
|
||||||
|
$migrationFiles = $this->getMigrationFiles();
|
||||||
|
$migrationToRollback = null;
|
||||||
|
|
||||||
|
foreach ($migrationFiles as $filename) {
|
||||||
|
$migrationName = $this->getMigrationNameFromFilename($filename);
|
||||||
|
if ($migrationName === $lastAppliedMigration['migration_name']) {
|
||||||
|
$migrationToRollback = $filename;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($migrationToRollback) {
|
||||||
|
$migrationClass = $this->loadMigrationClass($migrationToRollback);
|
||||||
|
|
||||||
|
if ($migrationClass && method_exists($migrationClass, 'down')) {
|
||||||
|
$migrationClass->down($this->pdo);
|
||||||
|
$this->markAsRolledBack($lastAppliedMigration['migration_name']);
|
||||||
|
echo "✓ Миграция {$lastAppliedMigration['migration_name']} успешно откачена\n";
|
||||||
|
} else {
|
||||||
|
echo "✗ Ошибка: Метод down() не найден в миграции {$lastAppliedMigration['migration_name']}\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "✗ Ошибка: Файл миграции не найден для {$lastAppliedMigration['migration_name']}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Убедиться, что таблица миграций существует
|
||||||
|
*/
|
||||||
|
private function ensureMigrationTableExists(): void
|
||||||
|
{
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS migrations (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
migration_name VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)";
|
||||||
|
|
||||||
|
$this->pdo->exec($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить список примененных миграций
|
||||||
|
*/
|
||||||
|
private function getAppliedMigrations(): array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->query("SELECT migration_name FROM migrations ORDER BY applied_at ASC");
|
||||||
|
return array_column($stmt->fetchAll(PDO::FETCH_ASSOC), 'migration_name');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить список файлов миграций
|
||||||
|
*/
|
||||||
|
private function getMigrationFiles(): array
|
||||||
|
{
|
||||||
|
$files = glob($this->migrationsDir . "*.php");
|
||||||
|
natsort($files); // Сортировка по алфавиту с учетом чисел
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Извлечь имя миграции из имени файла
|
||||||
|
*/
|
||||||
|
private function getMigrationNameFromFilename(string $filename): string
|
||||||
|
{
|
||||||
|
$basename = basename($filename, '.php');
|
||||||
|
return $basename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Загрузить класс миграции из файла
|
||||||
|
*/
|
||||||
|
private function loadMigrationClass(string $filename): ?object
|
||||||
|
{
|
||||||
|
if (!file_exists($filename)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once $filename;
|
||||||
|
|
||||||
|
$className = basename($filename, '.php');
|
||||||
|
|
||||||
|
// Попробуем найти класс в файле
|
||||||
|
$declaredClasses = get_declared_classes();
|
||||||
|
$migrationClass = null;
|
||||||
|
|
||||||
|
foreach ($declaredClasses as $declaredClass) {
|
||||||
|
if (strpos($declaredClass, $className) !== false) {
|
||||||
|
$migrationClass = $declaredClass;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($migrationClass && class_exists($migrationClass)) {
|
||||||
|
return new $migrationClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отметить миграцию как примененную
|
||||||
|
*/
|
||||||
|
private function markAsApplied(string $migrationName): void
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare("INSERT INTO migrations (migration_name) VALUES (?)");
|
||||||
|
$stmt->execute([$migrationName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отметить миграцию как откаченную
|
||||||
|
*/
|
||||||
|
private function markAsRolledBack(string $migrationName): void
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare("DELETE FROM migrations WHERE migration_name = ?");
|
||||||
|
$stmt->execute([$migrationName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить последнюю примененную миграцию
|
||||||
|
*/
|
||||||
|
private function getLastAppliedMigration(): ?array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->query("SELECT migration_name FROM migrations ORDER BY applied_at DESC LIMIT 1");
|
||||||
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue