Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ext Soap Engine Laravel Package

php-soap/ext-soap-engine

A SOAP engine for php-soap that builds clients from WSDL using PHP's native ext-soap extension. It generates type-safe client classes, handles requests and responses, and lets you swap engines without changing your application code.

Deep Wiki
Context7

Getting Started

Requirements: PHP 8.x with the native ext-soap extension enabled (php -m | grep soap). There are no extra runtime dependencies — the package just adapts \SoapClient behind php-soap's Engine interface.

Install:

composer require php-soap/ext-soap-engine
# optional, but this is what most projects actually talk to:
composer require php-soap/client

Look here first:

  • Soap\ExtSoapEngine\ExtSoapEngineFactory — entry point, builds an Engine from options.
  • Soap\ExtSoapEngine\ExtSoapOptions — fluent wrapper around ext-soap options array.
  • Soap\ExtSoapEngine\ExtSoapDriver — the adapter that owns the underlying \SoapClient.
  • ExtSoapClientInterface — the small surface used internally (call + last request/response).

Minimal first call (engine level):

use Soap\ExtSoapEngine\ExtSoapEngineFactory;
use Soap\ExtSoapEngine\ExtSoapOptions;

$engine = ExtSoapEngineFactory::fromOptions(
    Ext soapOptions::defaults('https://example.com/service?wsdl', [
        'trace' => true,
        'exceptions' => true,
    ])
);

$result = $engine->request('Add', ['intA' => 1, 'intB' => 2]);

Minimal first call (DX client):

use Soap\Client\Client;

$client = new Client($engine);
$result = $client->Add(['intA' => 1, 'intB' => 2]); // magic __call
// or explicitly: $client->call('Add', ['intA' => 1, 'intB' => 2]);

First use case: swapping an existing new \SoapClient($wsdl, $options) while keeping the same options array — then progressively adding a classmap, typed DTOs, middleware, and test doubles without changing the call sites.

Implementation Patterns

Bind one engine per remote service in the container. Engines are cheap wrappers, but WSDL parsing is not — build once and reuse:

$this->app->singleton('soap.crm', fn () => ExtSoapEngineFactory::fromOptions(
    ExtSoapOptions::defaults(storage_path('app/wsdl/crm.wsdl'), [
        'trace' => config('app.debug'),
        'exceptions' => true,
        'cache_wsdl' => WSDL_CACHE_DISK,
    ])
));

Prefer a local, versioned WSDL in production. Download it at build/deploy time and point defaults() at the file path. This removes a network hop, makes behaviour reproducible, and avoids "the vendor changed the WSDL" incidents. Override the endpoint separately via the location option.

Map WSDL types to your own DTOs instead of working with stdClass chains:

$options = ExtSoapOptions::defaults($wsdl)
    ->withClassMap(App\Soap\ClassMap::class)
    ->withTypeMap(App\Soap\TypeMap::class);

Then $result contains your domain objects; keep those classes dumb (public props, no constructor args required) so deserialization works.

Non-WSDL mode is supported — pass null as the WSDL and supply location + uri in the options array. Use this for tiny internal services where WSDL parsing cost outweighs the benefit. You lose metadata/autocompletion and must know the exact operation names.

Decorate the Engine for cross-cutting concerns. This is the cleanest extension point because it sits above ext-soap and works identically across engines:

final class LoggingEngine implements \Soap\Engine\Engine
{
    public function __construct(
        private \Soap\Engine\Engine $inner,
        private \Psr\Log\LoggerInterface $logger,
    ) {}

    public function request(string $operation, array $arguments): mixed
    {
        $start = microtime(true);
        try {
            return $this->inner->request($operation, $arguments);
        } finally {
            $this->logger->info('soap.call', [
                'operation' => $operation,
                'ms' => (int) ((microtime(true) - $start) * 1000),
            ]);
        }
    }

    public function getMetadata(): \Soap\Engine\Metadata\Metadata
    {
        return $this->inner->getMetadata();
    }
}

This is where retries, circuit breakers, metrics, and argument scrubbing belong.

Inspect metadata for discovery/validation via $engine->getMetadata(). Useful for building an admin screen that lists operations and their parameter types, or for asserting at boot that the WSDL actually contains the operation you're about to call.

Testing: don't mock \SoapClient (it's final-ish and awkward). Implement Soap\Engine\Engine with a stub engine returning canned responses, or capture a real (sanitized) response and replay it. Assert on the arguments your code passed. This is the single biggest day-to-day benefit of using php-soap abstractions over raw ext-soap.

Gotchas and Tips

  • CLI and FPM have different php.ini. ext-soap enabled in web but missing in php artisan / queue workers is the most common first-hour bug. Check both.
  • Always set exceptions => true. With the ext-soap default (false), failures come back as SoapFault objects rather than exceptions, which silently breaks php-soap's error model.
  • trace => true is required for debugging. Only then can you read __getLastRequest() / __getLastResponse() off the underlying SoapClient (accessible through ExtSoapDriver). Never leave it on in high-throughput production paths — it buffers full payloads in memory.
  • WSDL cache bites in both directions. php.ini's soap.wsdl_cache_enabled / soap.wsdl_cache_ttl still apply. Use WSDL_CACHE_NONE plus ExtSoapOptions::disableWsdlCache() in dev and tests, and an on-disk cache in prod. Stale-cache issues after a vendor WSDL update are classic "it works on my machine".
  • connection_timeout is a connect timeout only. There's no per-request read timeout in ext-soap options; enforce overall timeouts at the stream context (stream_context option, e.g. http.timeout) or above the engine.
  • TLS problems are solved through stream_context, not through Guzzle/cURL options — for example verify_peer, verify_peer_name, cafile. Corporate proxies also need proxy_host / proxy_port.
  • Classmap classes must be instantiable without required constructor arguments and expose public properties. Typed properties that can legitimately be null will blow up during deserialization — prefer nullable types. Also be wary of readonly/promoted constructor properties.
  • Arrays are painful. A single element often deserializes as a scalar instead of an array. Set features => SOAP_SINGLE_ELEMENT_ARRAYS and normalize at the boundary. SOAP_USE_XSI_ARRAY_TYPE matters if the server validates request types strictly.
  • This engine bypasses PSR-18. Any middleware you have wired into a PSR-18 transport (Guzzle middleware for auth, retries, logging) will not run. Port those concerns to an Engine decorator or to ext-soap's own options (login/password, stream_context).
  • WS-Security, MTOM, and SwA attachments are weak spots in native ext-soap. If you need signed/encrypted headers or binary attachments, plan for a different engine or a dedicated middleware package rather than fighting SoapHeader by hand.
  • __setSoapHeaders is sticky state. If you set headers on a shared client instance, they leak into subsequent calls (and across tenants in long-running workers/Octane). Scope engines per request, or reset headers explicitly.
  • Fault messages are terse. A SoapFault gives you faultcode/faultstring and little else; log the last request/response (requires trace) or you'll be debugging blind. SoapUI/Insomnia against the same WSDL is still the fastest way to confirm whether the problem is your payload or the server.
  • Memory: huge WSDLs plus trace plus large responses add up. Cap memory_limit awareness, stream where possible, and avoid logging full payloads in production.
  • Extension points, ranked: (1) Engine decorator, (2) ExtSoapOptions subclass for shared defaults, (3) custom TypeMap/ClassMap converters, (4) stream_context. If you find yourself patching \SoapClient behaviour, reconsider whether ext-soap is the right engine for that integration.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core