fergusean/nusoap
NuSOAP for Laravel: a lightweight wrapper around the NuSOAP library for building and consuming SOAP web services. Create SOAP servers or clients with WSDL support without requiring PHP's SOAP extension.
Install with Composer:
composer require fergusean/nusoap
This is a straight re-packaging of the classic NuSOAP library, so there is no Laravel service provider, facade, config file, or published assets. The package registers its classes via Composer's autoloader (typically a classmap pointing at a lib/ directory), so after composer dump-autoload the classes are available in the global namespace:
$client = new \nusoap_client('https://example.com/service?wsdl', 'wsdl');
$client->setCredentials($user, $pass);
$result = $client->call('GetCustomer', ['id' => 42]);
if ($client->getError()) {
// returns a string, not an exception
throw new \RuntimeException($client->getError());
}
Where to look first: open vendor/fergusean/nusoap/composer.json to confirm the autoload strategy, then skim lib/nusoap.php / lib/class.soap_client.php in the vendor tree for available methods (call, setEndpoint, setHeaders, setTimeout, setUseCurl, getError, debug_str). Because the package is unmaintained upstream, check its PHP compatibility against your app's PHP version before committing.
First use case: the canonical Laravel use is consuming a legacy SOAP/XML-RPC endpoint inside a queued job or a service class. Do a single call() inside php artisan tinker, dump $client->debug_str to see the raw request/response, and only then wire it into application code.
Wrap it in a dedicated service class. Never let nusoap_client leak into controllers. Bind a per-endpoint client through the container:
// app/Services/Legacy/CustomerSoapClient.php
class CustomerSoapClient
{
public function __construct(private array $config) {}
public function getCustomer(int $id): array
{
$client = new \nusoap_client($this->config['wsdl'], 'wsdl');
$client->setEndpoint($this->config['endpoint']); // override WSDL endpoint per env
$client->setCredentials($this->config['user'], $this->config['pass']);
$client->setTimeout(30);
$client->setUseCurl(true);
$result = $client->call('GetCustomer', ['id' => $id]);
if ($client->getError()) {
Log::warning('SOAP fault', [
'error' => $client->getError(),
'request' => $client->request,
'response'=> $client->response,
]);
throw new SoapCallException($client->getError());
}
return $result;
}
}
Register it in a service provider with singleton or scoped (one client per request is usually enough; clients are cheap to construct but expensive to reuse across credentials).
Config over hardcoding. Add config/services.php entries for WSDL URL, endpoint override, credentials, timeout, and verify_peer. This is essential because WSDL URLs often differ between sandbox and production, and NuSOAP will happily call the endpoint embedded in the WSDL if you forget setEndpoint.
Convert array results into DTOs immediately. NuSOAP returns nested associative arrays with inconsistent casing and sometimes wraps single elements differently from lists. Normalize once at the boundary, and the rest of your app stays SOAP-free.
For a SOAP server, use nusoap_server inside a route that bypasses CSRF and session middleware:
Route::post('/soap', function () {
$server = new \nusoap_server();
$server->configureWSDL('MyService', 'urn:myservice');
$server->register('GetOrder', [...], [...], 'urn:myservice');
$server->service(file_get_contents('php://input'));
})->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]);
Note the use of php://input rather than $HTTP_RAW_POST_DATA (see below).
Use setHeaders for WS-Security / custom SOAP headers. NuSOAP has no built-in WSSE support; you build the <soap:Header> XML string yourself and pass it in. Wrap this in a small helper so the XML isn't duplicated.
Mock by constructor injection. Because your wrapper class takes config and constructs the nusoap_client internally, tests can subclass the wrapper or swap a fake implementation bound in the container. Do not try to mock \nusoap_client itself; it is not final but its property surface is too large to be useful.
Queues and retries. SOAP calls are blocking and can be slow. Push them to a job, use $tries/backoff, and translate the string error into an exception so Laravel's retry machinery kicks in.
getError() check, or you'll silently return false/null. Wrap this in your service class.debug_str is a memory hog. It accumulates every request and response, including large XML payloads. Only enable it in non-production, or clear it after logging.$HTTP_RAW_POST_DATA / HTTP_RAW_POST_DATA global. Old NuSOAP servers read the request body from a global that was removed in PHP 7. Always pass file_get_contents('php://input') explicitly to service().each()/create_function() remnants in older tags, and loose parameter handling. Pin the package version and test on your target PHP before shipping; if you're on PHP 8.2+, run the test suite with E_ALL and fix/suppress deprecations early.nusoap_wsdlcache) that writes to a directory. If your WSDL changes and your calls suddenly break, clear the cache directory or disable caching. In ephemeral containers, make sure the cache path is writable or disabled.setEndpoint vs the WSDL endpoint. Without an explicit setEndpoint, NuSOAP uses the soap:address from the WSDL. In staging environments this often points to production — a classic incident. Always set it from config.verify_peer is set through NuSOAP's transport options (or $client->setCurlOption(...) when using cURL), not Laravel's Http config. Prefer setUseCurl(true) because the cURL transport handles TLS and proxies more predictably than the raw socket transport.'utf-8' in the constructor or via setHttpEncoding where supported.xsi:type annotations. When in doubt, dump $client->request and compare to a working SoapUI request.soap extension is faster and better supported if it's enabled; phpro/soap-client generates typed clients from a WSDL and integrates well with Laravel. Reach for NuSOAP specifically when the extension isn't available, when you need its WSDL-generation server side, or when you're maintaining a legacy integration — not for new greenfield SOAP clients.How can I help you explore Laravel packages today?