# رفع خطای `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 (خطوط ~62–73): ```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 تغییر کند لازم می‌شد).