6.1 KiB
رفع خطای 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 فقط این افزونهها نصب میشوند:
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 (خطوط ~62–73):
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:
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 اضافه کن.
نتیجهٔ مطلوب:
&& 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 دیده شود:
"require": {
// ... موجود
"ext-soap": "*"
}
توجه: در Dockerfile مرحلهٔ vendor از --ignore-platform-reqs استفاده میکند، پس این افزوده build را نمیشکند؛ صرفاً بهعنوان مستندسازی نیازمندی و اعتبارسنجی در محیطهای دیگر عمل میکند.
۳. تأیید و rebuild
بعد از تغییر Dockerfile، image پرود باید دوباره build/deploy شود (Coolify). برای تأیید نصب افزونه پس از build:
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 تغییر کند لازم میشد).