Files
clinicpro/.claude/prompt/fix-prod-soap-extension.md
T

6.1 KiB
Raw Blame History

رفع خطای 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 (خطوط ~6273):

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 تغییر کند لازم می‌شد).