# دیپلوی ClinicPro (Symfony) روی لیارا با پلتفرم PHP (بدون داکر)
## پروژه
`clinicpro` (Backend Symfony 7.4 + پنل ادمین React 19).
راهنما + تسک پیادهسازی برای دیپلوی روی **پلتفرم PHP لیارا** (buildpack بومی؛ لیارا خودش از سورس با `composer install` build میکند — بدون Dockerfile).
> منابع: مستندات لیارا — paas/php/how-tos (set-envs، use-disk، set-logs، customize-php-ini، customize-htaccess، use-queues، set-http-security-headers، use-hooks، set-cron-job) و paas/laravel (quick-start، set-envs، set-cron-job، choose-version، manage-logs).
---
## زمینه
تا الان دو مسیر دیپلوی برای این پروژه آماده شده:
- **داکر/Coolify:** `Dockerfile` + `docker-compose.yml`
- **داکر/لیارا:** `liara.json` (platform docker) + `liara-compose.yaml`
این پرامپت مسیر **سومی** را میسازد: **پلتفرم PHP لیارا** (نوع برنامه = `php` هنگام «ساخت برنامهی جدید»). اینجا داکری در کار نیست؛ لیارا کد را میگیرد، `composer install` میزند و روی Apache + PHP-FPM سرو میکند.
تفاوتهای کلیدی پلتفرم PHP که معماری را تعیین میکنند (از مستندات):
- **Hookها فایلهای شلاند، نه JSON:** `liara_pre_build.sh` (قبل build، **بدون** دسترسی به env، مناسب apt) و `liara_pre_start.sh` (قبل start، **با** env، مناسب migration).
- **Worker با `supervisor.conf`:** فایل `supervisor.conf` در ریشه؛ هر `[program]` یک دستور دلخواه (`php bin/console messenger:consume ...`) را زنده نگه میدارد. `$ROOT` = ریشهٔ اپ، `user=www-data`.
- **php.ini با `liara_php.ini`** در ریشه.
- **سرو با Apache + `.htaccess`؛ documentRoot باید `public/` شود.**
- **دیسکها** در `liara.json` با مسیر **absolute**.
- **نسخهٔ PHP:** 7.2 تا 8.4 (پیشفرض 7.4) — پروژه `php: >=8.2` میخواهد، پس باید 8.2+ انتخاب شود.
---
## مشکل / هدف
اجرای کامل Symfony روی پلتفرم PHP لیارا، شامل:
1. سرو front-controller سیمفونی از `public/` روی Apache.
2. اجرای migration و تولید کلید JWT در زمان استقرار.
3. اجرای دو مصرفکنندهٔ Messenger (`async` و `scheduler_default`) بهصورت پایدار.
4. آپلودِ assetهای buildشدهٔ React (که `composer install` آنها را نمیسازد).
5. اتصال به MariaDB و Redis مدیریتشدهٔ لیارا.
---
## فایلهای مرتبط
| فایل | نقش | وضعیت |
|------|-----|------|
| `liara.json` | پیکربندی دیپلوی | **بازنویسی برای platform php** (فعلاً platform docker است) |
| `public/.htaccess` | rewrite سیمفونی برای Apache | **وجود ندارد — ساخته شود** (apache-pack نصب نیست) |
| `liara_pre_start.sh` | hook قبل start (migration/JWT) | **جدید** |
| `supervisor.conf` | اجرای دو worker | **جدید** |
| `liara_php.ini` | تنظیم php.ini | **جدید** |
| `.liaraignore` | کنترل فایلهای آپلودی | **جدید** (تا `public/build` آپلود شود) |
| `composer.json` | اسکریپتهای نصب | بدون تغییر (با احتیاط؛ پایین) |
> ⚠️ **تداخل liara.json:** الان `liara.json` برای platform docker است. پلتفرم PHP به محتوای متفاوتی نیاز دارد. فقط **یک** مسیر را همزمان نگه دار. اگر PHP را انتخاب کردی، محتوای docker را با نسخهٔ PHP زیر جایگزین کن (یا برنچ جدا).
---
## وضعیت فعلی (کد واقعی)
`composer.json` هنگام نصب این اسکریپتها را اجرا میکند (روی build لیارا هم اجرا میشوند):
```json
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": ["@auto-scripts"]
```
`.gitignore` اینها را نادیده میگیرد (پس بهصورت پیشفرض آپلود **نمیشوند**):
```
/public/build/ # ← خروجی yarn build؛ باید آپلود شود
/config/jwt/*.pem # ← روی دیسک ساخته میشود
/var/
.env
```
`public/.htaccess` وجود ندارد؛ extensionهای موردنیاز Symfony: `pdo_mysql`, `intl`, `opcache`, و برای Messenger روی Redis: `redis` (phpredis).
---
## وظایف
### ۱. ساخت `liara.json` برای پلتفرم PHP
محتوای فعلی (platform docker) را با این جایگزین کن:
```json
{
"app": "clinicpro-api",
"platform": "php",
"port": 80,
"php": {
"version": "8.2"
},
"documentRoot": "public",
"disks": [
{ "name": "jwt", "mountTo": "/var/www/config/jwt" },
{ "name": "uploads", "mountTo": "/var/www/public/uploads" },
{ "name": "var-uploads", "mountTo": "/var/www/var/uploads" }
]
}
```
نکات:
- `documentRoot: "public"` تا Apache front-controller سیمفونی را سرو کند.
- مسیر mount دیسکها **absolute** است؛ ریشهٔ اپ روی پلتفرم PHP معمولاً `/var/www` است — اگر در کنسول/لاگ مسیر دیگری دیدی، اصلاح کن.
- کلید `php.version` و `documentRoot` را اگر کنسول نپذیرفت، از تنظیمات کنسول (Platform Settings) ست کن — مستند نسخهگزینی را از طریق کنسول هم ممکن میداند.
- سه دیسک `jwt` / `uploads` / `var-uploads` را پیش از دیپلوی در کنسول بساز.
### ۲. ساخت `public/.htaccess` (rewrite سیمفونی)
چون `symfony/apache-pack` نصب نیست، یا نصبش کن:
```bash
ddev exec composer require symfony/apache-pack
```
(این بهصورت تعاملی `public/.htaccess` استاندارد سیمفونی را میسازد) — یا فایل را دستی بساز:
```apache
RewriteEngine On
RewriteCond %{REQUEST_URI}::$0 ^(/.+)/(.*)::\2$
RewriteRule .* - [E=BASE:%1]
RewriteCond %{HTTP:Authorization} .+
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
RewriteCond %{ENV:REDIRECT_STATUS} =""
RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
RewriteRule ^ %{ENV:BASE}/index.php [L]
RedirectMatch 307 ^/$ /index.php/
```
### ۳. ساخت `supervisor.conf` (دو worker مسنجر)
روی پلتفرم PHP، خود لیارا Apache/PHP-FPM را اجرا میکند؛ `supervisor.conf` **فقط** workerها را اضافه میکند (نه وب). در ریشه بساز:
```ini
[program:worker-async]
process_name=%(program_name)s_%(process_num)02d
command=cd $ROOT && php bin/console messenger:consume async --time-limit=3600 --memory-limit=128M -v
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
numprocs=1
user=www-data
redirect_stderr=true
stdout_logfile=/tmp/worker-async.log
[program:worker-scheduler]
process_name=%(program_name)s_%(process_num)02d
command=cd $ROOT && php bin/console messenger:consume scheduler_default --time-limit=3600 -v
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
numprocs=1
user=www-data
redirect_stderr=true
stdout_logfile=/tmp/worker-scheduler.log
```
> جایگزینِ cron: اگر نخواهی scheduler بهصورت worker بماند، میتوانی همان کار را با Cron Job کنسول (`* * * * *` → دستوری که یک پیام scheduler را مصرف کند) انجام دهی. ولی worker async (SMS) حتماً باید در `supervisor.conf` بماند.
### ۴. ساخت `liara_pre_start.sh` (migration + JWT + cache)
در ریشه، با env در دسترس:
```bash
#!/bin/sh
set -e
echo "pre-start: waiting for database..."
i=0
until php bin/console doctrine:query:sql "SELECT 1" >/dev/null 2>&1; do
i=$((i + 1))
[ "$i" -ge 30 ] && { echo "DB not reachable after 60s" >&2; exit 1; }
sleep 2
done
# کلید JWT روی دیسک ماندگار jwt
php bin/console lexik:jwt:generate-keypair --skip-if-exists --no-interaction
php bin/console cache:clear --no-warmup
php bin/console cache:warmup
php bin/console doctrine:migrations:migrate --all-or-nothing --no-interaction
```
فایل را اجرایی کن: `chmod +x liara_pre_start.sh`.
### ۵. ساخت `liara_php.ini`
برای پشتیبانی از آپلود (سقف اپ ۵MB ولی هدر/چندبخشی بزرگتر) و زمان اجرا:
```ini
memory_limit = 256M
upload_max_filesize = 16M
post_max_size = 24M
max_execution_time = 120
file_uploads = On
```
### ۶. ساخت `.liaraignore` تا assetهای build آپلود شوند
`.gitignore` پوشهٔ `/public/build/` را نادیده میگیرد و لیارا بهصورت پیشفرض از gitignore پیروی میکند → assetها آپلود نمیشوند. با ساخت `.liaraignore` کنترل را بهدست بگیر (فقط واقعاً غیرلازمها را کنار بگذار، `public/build` را **نگه دار**):
```
.git
.ddev
.claude
.github
.vscode
node_modules
tests
var/cache
var/log
docs
graphify-out
*.sql
*.sql.gz
.DS_Store
```
پیش از هر دیپلوی، assetها را **محلی** build کن (لیارا `yarn build` نمیزند):
```bash
ddev exec yarn install --frozen-lockfile
ddev exec yarn build # خروجی در public/build
```
### ۷. دیتابیس و Redis مدیریتشده + متغیرهای محیطی
مثل مسیر داکر:
1. در کنسول، **MariaDB 11.8** و **Redis** بساز، شبکهٔ خصوصی فعال، برنامه را به همان شبکه وصل کن.
2. envها را با `liara env set ... --app clinicpro-api` یا تب Environment ست کن (مرجع کامل: `.env.liara.example`). حداقل:
- `APP_ENV=prod`, `APP_DEBUG=0`
- `APP_SECRET`, `JWT_PASSPHRASE` (ثابت)
- `DATABASE_URL` (هاست خصوصی MariaDB، `serverVersion=mariadb-11.8.0`)
- `REDIS_URL`, `MESSENGER_TRANSPORT_DSN` (هاست خصوصی Redis)
- `JWT_SECRET_KEY`/`JWT_PUBLIC_KEY` (مسیر `config/jwt/*.pem`)
- `APP_BASE_URL`, `DEFAULT_URI`, `TRUSTED_PROXIES`
- `ALLOWED_FRONTEND_HOSTS`, `CORS_ALLOW_ORIGIN` (خروجی `php docker/gen-cors-env.php`)
- `API_IR_BASE_URL`, `API_IR_TOKEN`
3. **مهم:** `APP_ENV=prod` باید موقع **build** هم موجود باشد، چون `composer install` اسکریپت `cache:clear` را اجرا میکند.
### ۸. استقرار و تأیید
```bash
liara deploy # از ریشهٔ clinicpro، با liara.json platform php
```
سپس:
- `GET https:///health` → 200
- لاگ: در کنسول/`liara logs` هر دو worker (`worker-async`, `worker-scheduler`) بالا باشند
- ورود `/admin` و تست یک endpoint
---
## نکات مهم
- **ext-redis ریسک اصلی است:** اگر پلتفرم PHP لیارا افزونهٔ `redis` (phpredis) را نداشته باشد، `REDIS_URL` و `MESSENGER_TRANSPORT_DSN: redis://...` کار نمیکنند. اول در محیط لیارا چک کن (`php -m | grep redis`). اگر نبود:
- برای Messenger به transport مبتنی بر **Doctrine** سوییچ کن (`MESSENGER_TRANSPORT_DSN=doctrine://default`) و جدول مسنجر را با migration بساز،
- برای cache هم به adapter فایلسیستم/Doctrine برگرد.
- **اسکریپتهای composer در build:** `cache:clear` در prod بدون DB معمولاً سالم است؛ اگر build به خاطر اسکریپتها شکست، میتوان نصب را بدون اسکریپت کرد و همهچیز را به `liara_pre_start.sh` سپرد (هماهنگ با کاری که `Dockerfile` با `--no-scripts` میکند).
- **ماندگاری کلید JWT:** دیسک `jwt` حتماً mount شود؛ وگرنه هر دیپلوی کلید نو میسازد و توکنها باطل میشوند.
- **assetها فراموش نشوند:** اگر `public/build` آپلود نشود، پنل ادمین لود نمیشود — قبل دیپلوی `yarn build` و `.liaraignore` درست.
- **documentRoot:** اگر روی `public` ست نشود، Apache فایلهای ریشه را سرو میکند و اپ ۴۰۴/۴۰۳ میدهد.
- **هماهنگی نسخهٔ DB:** `serverVersion` در `DATABASE_URL` با نسخهٔ MariaDB مدیریتشده یکی باشد (11.8).
- این تغییر فقط زیرساخت دیپلوی است و هیچ route/endpoint را عوض نمیکند → نیازی بهروزرسانی `docs/api/*` نیست.
- **یک مسیر را انتخاب کن:** Docker (`liara.json` فعلی + `liara-compose.yaml`) یا PHP platform (این پرامپت). همزمان هر دو `liara.json` نگهداشتنی نیست.
```