Вы когда-нибудь забывали внести изменения в базу данных после публикации нового функционала? А еще эти кеши модификаторов, которые нужно не забыть обновить. В этой статье я опишу, как автоматизировать процесс деплоя проекта и избавить себя от рутины.
Данная статья предполагает, что вы используете Git как систему контроля версий, GitHub как хранилище вашего репозитория и также у вас есть SSH доступ к серверу, на котором можно запустить composer. В качестве основы для нашей автоматизации мы будем использовать GitHub Actions, который позволяет запускать различные процессы внутри контейнеров.
Что нам понадобится сделать:
консольные скрипты для очистки модификаторов и кеша системы
систему миграций для БД
Консольные команды
Конечно, можно использовать исходный код опенкарта и на основе него написать скрипты, но это не наш метод. Мы напишем собственные велосипеды и за основу возьмем Symfony Console.
Устанавливаем нужные зависимости:
composer require symfony/console
Создаем директорию src для нашего приложения (лучше всего размещать такой код на уровень выше вашего index.php)
В composer.json, в разделе автозагрузки добавим код для задания пространства имен нашего приложения:
"autoload": {
"psr-4": {
"App\\": "путь к папке/src/",
}
},
"require": {
...список ваших зависимостей...,
}
Создадим папки в директории src для будущего кода: Commands, Models, Services, Traits
Структура папки src
Создадим сервисы для очистки директорий кеша
Базовый класс
<?php
declare(strict_types=1);
namespace App\Services\Clear;
use FilesystemIterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
class BaseClearService
{
protected const SKIP_FILE = 'index.html';
public function clear(string $dir): void
{
$directories = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($directories, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files as $file) {
if ($file->isFile() && $file->getFilename() === self::SKIP_FILE) {
continue;
}
$file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath());
}
}
}
Сервис очистка системного кеша
<?php
declare(strict_types=1);
namespace App\Services\Clear;
class CacheClearService extends BaseClearService
{
public function process(): void
{
$this->clear(DIR_CACHE);
}
}
Сервис очистки старых модификаторов
<?php
declare(strict_types=1);
namespace App\Services\Clear;
class ModificationClearService extends BaseClearService
{
public function process(): void
{
$this->clear(DIR_MODIFICATION);
}
}
ModificationService - мне было лень переписывать код, взял из опенкарта как есть.
Первый сервис очищает директорию, а второй создает модифицируемые файлы. Для создания файлов нам нужно из базы получить список модификаторов. Для этой цели можно использовать разные подходы, но т.к. я использую в других проектах Laravel, я задействую для работы с базой Eloquent .
Установим пакет:
composer require illuminate/database
Создаем файл модели модификаций:
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Modification extends Model
{
protected $table = 'modification';
protected $primaryKey = 'modification_id';
public $timestamps = false;
}
Файлы для работы с базой данных
<?php
namespace App\Traits;
trait Singleton
{
public static $instance;
public static function getInstance(): self
{
if (empty(self::$instance)) {
self::$instance = new static();
}
return self::$instance;
}
private function __clone()
{
}
private function __wakeup()
{
}
}
Теперь непосредственно создадим классы консольных команд для очистки системного кеша
<?php
declare(strict_types=1);
namespace App\Commands;
use App\Services\CacheClearService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CacheClearCommand extends Command
{
protected function configure(): void
{
$this->setName('cache:clear')
->setDescription('Clear system cache');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
(new CacheClearService())->process();
} catch (\Throwable $e) {
$output->writeln('<error>Directory error</error>');
return Command::FAILURE;
}
$output->writeln('<info>Modification cache cleared successfully</info>');
return Command::SUCCESS;
}
}
И для очистки кеша модификаторов
<?php
declare(strict_types=1);
namespace App\Commands\Opencart;
use App\Models\Database;
use App\Services\ModificationClearService;
use App\Services\ModificationService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ModificationClearCommand extends Command
{
protected function configure(): void
{
$this->setName('cache:modification')
->setDescription('Clear modification cache');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
Database::getInstance();
(new ModificationClearService())->process();
(new ModificationService())->process();
} catch (\Throwable $e) {
$output->writeln('<error>Directory deleting error</error>');
return Command::FAILURE;
}
$output->writeln('<info>Modification cache cleared successfully</info>');
return Command::SUCCESS;
}
}
Соберем все в общее консольное приложение
<?php
declare(strict_types=1);
namespace App\Commands;
use App\Commands\CacheClearCommand;
use App\Commands\ModificationClearCommand;
use Exception;
use Symfony\Component\Console\Application;
class AppConsole
{
/**
* @throws Exception
*/
public function run(): void
{
$app = new Application();
$app->add(new ModificationClearCommand());
$app->add(new CacheClearCommand());
$app->run();
}
}
И создадим файл console в корне, который будет запускать саму консоль
#!/usr/bin/env php
<?php
require dirname(__DIR__) . '/путь к/vendor/autoload.php';
require __DIR__ . '/путь к/admin/config.php';
use App\Commands\AppConsole;
(new AppConsole())->run();
Делаем его исполняемым: chmod +x console
Теперь при запуске команды php console мы получим список доступных команд:
php console cache:clear - очистка системного кеша php console cache:modification - очистка модификаторов
Таким образом можно создать другие команды: для сжатия картинок, для бэкапов базы и т.д.
Миграции баз данных
Данный подход позволяет фиксировать все изменения базы в PHP-коде. Следовательно, мы можем в Git хранить всю историю изменений базы данных.
Мы будем использовать библиотеку phinx. Установим ее:
composer require robmorgan/phinx
Далее запускаем команду для создания конфигурационного файла phinx.php
./vendor/bin/phinx init
Создадим папку db/migrations - тут будут храниться файлы миграций.
В конфиге phinx.php прописываем путь для папки, где будут лежать файлы миграций и параметры подключения к БД
<?php
return
[
'paths' => [
'migrations' => '%%PHINX_CONFIG_DIR%%/путь до папки db/migrations',
'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds'
],
'environments' => [
'default_migration_table' => 'phinxlog',
'default_environment' => 'development',
'development' => [
'adapter' => 'mysql',
'host' => 'host db',
'name' => 'opencart',
'user' => 'admin',
'pass' => '1234567',
'port' => '3306',
'table_prefix' => 'oc_',
'charset' => 'utf8mb4',
],
],
'version_order' => 'creation'
];
Ключ environments позволяет прописывать различные подключения к базе на проде или тестовом сервере и потом при запуске указывать, для какого окружения нужно запускать миграции.
Чтобы создать миграцию, запустите команду create и укажите название класса миграции, в названии которого будет отображена суть (для какой таблицы мы создаем миграцию).
Например, создадим миграцию для таблицы с постами:
./vendor/bin/phinx create PostsTableMigration
После чего в папке db/migrations появится файл с временной меткой в названии файла и именем миграции с snake case. Заполним файл:
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class DocsMigration extends AbstractMigration
{
public function change(): void
{
$table = $this->table('docs');
$table->addColumn('route', 'string')
->addColumn('description', 'text')
->addColumn('created', 'datetime', ['default' => 'CURRENT_TIMESTAMP'])
->addIndex(['route'], ['unique' => true])
->create();
}
}
Тут все просто, указываем название таблицы, колонки и их типы, задаем индексы. Для каждой таблицы автоматически создается автоинкрементное поле id. Полную инструкцию по написанию миграций можно найти на оф. сайте: https://book.cakephp.org/phinx/0/en/migrations.html
Для запуска миграций воспользуемся командой:
./vendor/bin/phinx migrate
После запуска данного скрипта в таблице phinxlog (настраивается в параметре default_migration_table из файла phinx.php) появится запись о примененной миграции.
GitHub Actions
Остался последний и самый главный этап - настройка на стороне GitHub.
Создаем в корне проекта файл (указываю полный путь) .github/workflows/deploy.yml. Здесь будут указаны все задачи, которые должны будут запускаться внутри контейнеров на стороне GitHub.
name: Deploy
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
script: |
cd /путь к корню проекта
./.scripts/deploy.sh
./vendor/bin/phinx migrate
Тут мы указываем на что будет реагировать наш Action. В данном случае на пуш в ветку main
on:
push:
branches: [ main ]
jobs - непосредственно описываем задачи deploy - имя задачи appleboy/ssh-action@master - инструмент для запуска удаленных команд по SSH. Ниже указаны параметры для аутентификации и список выполняемых команд:
- cd - переходим в корень проекта
- ./.scripts/deploy.sh - запускаем баш-скрипт с нашими командами (опишем ниже)
- ./vendor/bin/phinx migrate - запускаем миграции
appleboy/ssh-action@master - для аутентификации можно использовать ssh-юзера и пароль, но я указал настройки для входа по ключу.
Для этого зайдем по SSH на сервер, куда мы будем деплоить проект и создадим связку ключей:
ssh-keygen -t rsa -b 4096 -C "
[email protected]"
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
Скопируем приватный ключ
cat ~/.ssh/id_rsa
Теперь нужно заполнить секретные ключи в GitHub.
Переходим в наш репозиторий: Settings -> Secrets and variables -> Actions и добавляем наши секреты: HOST - хост сервера PORT - порт сервера KEY - скопированный ранее приватный ключ USERNAME - имя пользователя на сервере
Создаем баш-скрипт .scripts/deploy.sh для запуска консольных команд, которые мы создавали в начале статьи.
#!/bin/bash
set -e
echo "Deployment started ..."
git pull
php console cache:modification
php console cache:clear
echo "Deployment finished!"
Тут мы забираем все изменения из git-репозитория и чистим кеши нашими консольными скриптами. Сюда можно добавить команду для включения режима обслуживания на время деплоя, но это оставлю вам для самостоятельного выполнения.
Разрешаем запуск данного скрипта
chmod +x deploy.sh
Окончательная структура директорий
Теперь мы можем запушить новые изменения в репозиторий и увидим, как во вкладке Actions на GitHub появится новая задача. Мы можем нажать на нее и посмотреть детально на все происходящие процессы в контейнерах.
Если произойдет ошибка, то вам на почту придет письмо, что Action завершился неудачно.
Сюда также можно добавить проверки на codestyle, запуск тестов и построить полноценное CI/CD, но это пока не про Opencart...