Внедрение db (pgsql/mysql/sqlite)
Одна из самых распространенных зависимостей это реляционная БД, которая может быть представлена различными решениями: pgsql, mysql, sqlite и тд.
Аналогичным способом можно внедрять:
- очереди (kafka, rabbitmq и тд),
- быстрые nosql базы (redis, dragonfly, valkey, memcached, mongadbи тд),
- поисковые движки (opensearch, elasticksearch, sphinx и тд),
- колоночные базы для аналитки (clickhouse и тд)
- файловые хранилища s3 (minio и тд).
Рассмотрим максимально простой вариант по настройке postgres.
-
Определяемся с целями.
- В разработке, у каждого разработчика свой сервис postgres:18.
- В production, пусть это будет managed кластер, нам предоставляют credentials для входа.
- Возможность изменять credential после продуктовой сборки.
- В коде внедряем PDO как зависимость.
- Для работы PDO требуется extension pdo, pdo-pgsql.
-
Настрайваем docker image.
Dockerfile
FROM php:8.4-cli-alpine AS base # ... RUN install-php-extensions \ pdo \ pdo_pgsql \ # ... # ...Устанавливать надо именно в base чтобы эти extension были доступны как в dev, так и prod сборке.
-
Настрайваем сервисы для разработки.
docker-compose.yml
services: app: # ... depends_on: db: condition: service_healthy db: image: postgres:18-alpine3.22 environment: - PGUSER=postgres # user for pg_isready - POSTGRES_PASSWORD=cekta ports: - "5432:5432" healthcheck: test: [ "CMD-SHELL", "pg_isready" ] interval: 10s timeout: 5s retries: 5Мы добавили новый сервис db, указали пароль
cekta, описали healthcheck, указали зависимость app от db. -
Обновляем окружение разработки и запускаем его
make refresh make dev -
Используем зависимости в своем коде.
src/Test.php
<?php declare(strict_types=1); namespace App; use Cekta\Framework\HTTP\Response\JSONFactory; use Cekta\Framework\HTTP\Route; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; #[Route\GET('/test')] final readonly class Test implements RequestHandlerInterface { public function __construct( private JSONFactory $factory, private \PDO $pdo, ) { } public function handle(ServerRequestInterface $request): ResponseInterface { return $this->factory->create( $this->pdo->query('SHOW ALL') ->fetchAll(\PDO::FETCH_ASSOC) ); } }Он отображает текущие настройки postgres, так как для нормальной работы с бд надо инструмент миграций, для упрощения пока пропустим, сделаем это отдельно.
-
Настрайваем
App\Module.// ... public function onCreateParameters(mixed $cachedData): array { return [ \PDO::class . '$username' => $this->env['DB_USERNAME'] ?? 'postgres', \PDO::class . '$password' => $this->env['DB_PASSWORD'] ?? 'cekta', \PDO::class . '$dsn' => new \Cekta\DI\Lazy\Closure(function (ContainerInterface $c) { $host = $this->env['DB_HOST'] ?? 'db'; $db = $this->env['DB_NAME'] ?? 'postgres'; return "pgsql:host=$host;dbname=$db;"; }), ]; }Это production ready вариант конфигурации, чтобы была возможность изменять credential после релиза, при этом в разработке используются удобные параметры, небольшое объяснение:
PDO зависит от 4 аргументов (dsn, username, password, options), один из аргументов обязательный (dsn), остальные опциональные (имеют значения по умолчанию).
Итого нам необходимо определить 3 параметра: dsn, username, password.\PDO::class . '$username'- для зависимости с именем\PDO::classаргумент с именемusernameпередать следующее значение, которое указано справа.this->env['DB_USERNAME'] ?? 'postgres'- если в environment указано DB_USERNAME то используем его, в остальных случаях используем значение по умолчаниюpostgres.Обратите внимание на значение
dsn, если значение реализует интерфейсCekta\DI\Lazyзначит значение вычисляется после сборки в runtime, в данном случае значение генерируется callback функцией, которая генерирует dsn строку, на основе параметров из env или значений по умолчанию. -
build или restart окружения.
make restart -
Проверяем результат.
Открываем http://localhost:8080/test - Получим текущие настройки postgres.