fix: add ext-soap to Dockerfile and composer.json to resolve SoapClient not found error in production

This commit is contained in:
hamed
2026-07-08 09:37:19 +03:30
parent a2c809b7de
commit 5e5455cf8c
3 changed files with 117 additions and 2 deletions
+114
View File
@@ -0,0 +1,114 @@
# رفع خطای `Class "SoapClient" not found` در پرداخت ملت (سرور prod)
## پروژه
`clinicpro` (backend / infra — Docker image)
## زمینه
درگاه ملت (`MellatGateway`) در مسیر prod برای فراخوانی وب‌سرویس بانک از `new \SoapClient(...)` استفاده می‌کند (کلاس PHP از افزونه `ext-soap`). روی محیط لوکال (ddev) افزونه `soap` به‌صورت پیش‌فرض نصب است، پس مشکلی دیده نمی‌شود؛ اما image پرود بر پایه `php:8.2-fpm-alpine` ساخته می‌شود و در `Dockerfile` فقط این افزونه‌ها نصب می‌شوند:
```dockerfile
docker-php-ext-install pdo_mysql intl opcache
```
`soap` نصب نمی‌شود، بنابراین در پرود هنگام initiate کردن پرداخت، fatal می‌شود:
```
Payment initiate failed (mellat): Class "SoapClient" not found @ /app/src/Payment/Gateway/MellatGateway.php:70
context {"orderId":"6","amount":150000}
```
## مشکل / هدف
افزونه `ext-soap` روی image پرود نصب شود تا مسیر prod درگاه ملت (`bpPayRequest` / `bpVerifyRequest` / `bpSettleRequest` / `bpRefundRequest` / `bpReversalRequest`) روی `SoapClient` کار کند.
نکته مهم: sandbox این مشکل را ندارد چون مسیر sandbox از REST (JSON + Basic Auth) استفاده می‌کند نه SOAP. مشکل فقط در حالت prod (فلگ `mellat_sandbox` غیرفعال) رخ می‌دهد.
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `Dockerfile` (stage 3 — runtime) | نصب افزونه‌های PHP؛ باید `soap` اضافه شود |
| `src/Payment/Gateway/MellatGateway.php` | خط ۷۰: `new \SoapClient($this->wsdlUrl(), ...)` — نیازمند `ext-soap` |
| `composer.json` | افزودن `ext-soap` به `require` تا نبود افزونه زودتر (build-time) شناسایی شود |
## وضعیت فعلی
`Dockerfile` — بلوک نصب افزونه‌ها در stage runtime (خطوط ~6273):
```dockerfile
RUN set -eu; \
export MAKEFLAGS="-j$(nproc)"; \
retry() { for i in 1 2 3 4 5; do "$@" && return 0; echo "retry $i: $*"; sleep 5; done; return 1; }; \
retry apk add --no-cache nginx supervisor icu-libs \
&& retry apk add --no-cache --virtual .build-deps $PHPIZE_DEPS icu-dev \
&& docker-php-ext-install pdo_mysql intl opcache \
&& retry curl -sSLf "https://github.com/phpredis/phpredis/archive/refs/tags/${PHPREDIS_VERSION}.tar.gz" -o /tmp/phpredis.tar.gz \
&& mkdir -p /usr/src/php/ext/redis \
&& tar -xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1 \
&& docker-php-ext-install redis \
&& rm -rf /tmp/phpredis.tar.gz \
&& apk del .build-deps
```
`MellatGateway.php:67-78`:
```php
private function soap(): \SoapClient
{
if ($this->soap === null) {
$this->soap = new \SoapClient($this->wsdlUrl(), [
'trace' => true,
'exceptions' => true,
'encoding' => 'UTF-8',
'connection_timeout' => 10,
]);
}
return $this->soap;
}
```
## وظایف
### ۱. افزودن `soap` به `docker-php-ext-install` در `Dockerfile`
افزونهٔ `soap` جزو افزونه‌های bundled خودِ PHP است و نیاز به شبکه (pecl/github) ندارد؛ فقط برای کامپایل به هدرهای `libxml2-dev` نیاز دارد. `libxml2-dev` را به `.build-deps` اضافه کن (تا بعد از build با `apk del .build-deps` پاک شود) و `soap` را به لیست `docker-php-ext-install` اضافه کن.
نتیجهٔ مطلوب:
```dockerfile
&& retry apk add --no-cache --virtual .build-deps $PHPIZE_DEPS icu-dev libxml2-dev \
&& docker-php-ext-install pdo_mysql intl opcache soap \
```
نکته: `libxml2` (کتابخانهٔ runtime، بدون `-dev`) در image پایهٔ PHP موجود است، پس افزونهٔ کامپایل‌شده در runtime بدون مشکل load می‌شود و نیازی به افزودن آن به لیست runtime نیست. فقط هدرهای `-dev` که در build لازم‌اند در `.build-deps` قرار می‌گیرند.
### ۲. اعلام نیازمندی در `composer.json`
`ext-soap` را به بخش `require` اضافه کن تا اگر روی محیطی افزونه نبود، در زمان `composer install` هشدار/خطا داده شود و مشکل زودتر از runtime دیده شود:
```jsonc
"require": {
// ... موجود
"ext-soap": "*"
}
```
توجه: در `Dockerfile` مرحلهٔ vendor از `--ignore-platform-reqs` استفاده می‌کند، پس این افزوده build را نمی‌شکند؛ صرفاً به‌عنوان مستندسازی نیازمندی و اعتبارسنجی در محیط‌های دیگر عمل می‌کند.
### ۳. تأیید و rebuild
بعد از تغییر `Dockerfile`، image پرود باید دوباره build/deploy شود (Coolify). برای تأیید نصب افزونه پس از build:
```bash
php -m | grep -i soap # باید soap را چاپ کند
php -r 'var_dump(class_exists("SoapClient"));' # bool(true)
```
## نکات مهم
- مشکل صرفاً محیطی (نبود افزونه در image پرود) است، نه باگ منطقی در کد `MellatGateway`. کد درست است؛ فقط وابستگی runtime روی پرود غایب بود.
- بعد از deploy، پرداخت واقعی ملت (حالت prod، فلگ `mellat_sandbox=0`) باید یک‌بار تست شود تا مسیر SOAP (`bpPayRequest`) واقعاً پاسخ بگیرد.
- الگوی موجود در `Dockerfile` را رعایت کن: build-deps مجازی که بعد از کامپایل با `apk del .build-deps` حذف می‌شوند تا حجم image کم بماند. `libxml2-dev` را داخل همین گروه بگذار، نه به‌صورت دائمی.
- این تغییر API را عوض نمی‌کند؛ نیازی به به‌روزرسانی `docs/api/payment.md` نیست (فقط اگر رفتار/قرارداد endpoint تغییر کند لازم می‌شد).
+2 -2
View File
@@ -63,8 +63,8 @@ RUN set -eu; \
export MAKEFLAGS="-j$(nproc)"; \ export MAKEFLAGS="-j$(nproc)"; \
retry() { for i in 1 2 3 4 5; do "$@" && return 0; echo "retry $i: $*"; sleep 5; done; return 1; }; \ retry() { for i in 1 2 3 4 5; do "$@" && return 0; echo "retry $i: $*"; sleep 5; done; return 1; }; \
retry apk add --no-cache nginx supervisor icu-libs \ retry apk add --no-cache nginx supervisor icu-libs \
&& retry apk add --no-cache --virtual .build-deps $PHPIZE_DEPS icu-dev \ && retry apk add --no-cache --virtual .build-deps $PHPIZE_DEPS icu-dev libxml2-dev \
&& docker-php-ext-install pdo_mysql intl opcache \ && docker-php-ext-install pdo_mysql intl opcache soap \
&& retry curl -sSLf "https://github.com/phpredis/phpredis/archive/refs/tags/${PHPREDIS_VERSION}.tar.gz" -o /tmp/phpredis.tar.gz \ && retry curl -sSLf "https://github.com/phpredis/phpredis/archive/refs/tags/${PHPREDIS_VERSION}.tar.gz" -o /tmp/phpredis.tar.gz \
&& mkdir -p /usr/src/php/ext/redis \ && mkdir -p /usr/src/php/ext/redis \
&& tar -xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1 \ && tar -xf /tmp/phpredis.tar.gz -C /usr/src/php/ext/redis --strip-components=1 \
+1
View File
@@ -9,6 +9,7 @@
"php": ">=8.2", "php": ">=8.2",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"ext-soap": "*",
"doctrine/doctrine-bundle": ">=2.18.3", "doctrine/doctrine-bundle": ">=2.18.3",
"doctrine/doctrine-migrations-bundle": "*", "doctrine/doctrine-migrations-bundle": "*",
"doctrine/orm": "^3.6", "doctrine/orm": "^3.6",