php-soap/wsdl
php-soap/wsdl provides a modern PHP API for reading and navigating WSDL documents. Inspect services, bindings, ports, operations, and schemas, resolve imports, and power SOAP clients or code generation without manual XML parsing.
Install the library standalone — it has no framework coupling:
composer require php-soap/wsdl
Then parse a WSDL and start inspecting it. The typical entry point is a reader/parser that accepts either a URI (local file or remote URL) or a raw XML string:
use PhpSoap\Wsdl\WsdlReader; // exact namespace/class may vary by minor version
$wsdl = WsdlReader::fromUri(__DIR__ . '/fixtures/service.wsdl');
foreach ($wsdl->services() as $service) {
echo $service->name() . PHP_EOL;
foreach ($service->ports() as $port) {
echo ' port: ' . $port->name() . PHP_EOL;
}
}
Where to look first:
src/ for the object-model classes — you will mostly live in Definitions, Service, Port, Binding, and Operation types. Read those first; they define what the parser can actually give you.tests/ (and any tests/fixtures/*.wsdl) for real, working usage examples. Fixtures double as a sanity check that your WSDL is the kind the parser understands.php-soap ecosystem packages (php-soap/xml, php-soap/wsdl-reader, transports) if you need schema handling or actual SOAP calls — this package is the object model, not a client.First use case: introspect a third-party SOAP service before writing any integration code — enumerate its operations, list ports/endpoints, and confirm you understand its contract. That single workflow replaces a pile of ad-hoc SimpleXMLElement traversal.
Build a contract inventory in a command/console task. Parse the WSDL once, iterate services → ports → bindings → operations, and emit a table or JSON of operation names plus their SOAP action and endpoint. This is the most common "day one" use of the package and is usually cheaper than reading vendor PDFs.
Generate code or config from the object model. Feed operation names and port endpoints into your own generator (route maps, client stubs, allowlists). Because the model is typed and predictable, the generator is testable: feed a fixture WSDL, assert the emitted config.
Parse local vs remote with one API. Use the URI form for remote WSDLs and the string form when you already have XML (from cache, a fixture, or a fetched-and-signed response). Keeping a single entry point avoids two code paths:
$wsdl = str_starts_with($location, 'http')
? WsdlReader::fromUri($location)
: WsdlReader::fromFile($location);
Lean on import resolution rather than merging files yourself. WSDL files split services, bindings, and schemas across wsdl:import documents (and relative paths). Let the library resolve them instead of concatenating XML. For local development this means keeping sibling .wsdl files in the same directory layout the vendor ships, so relative imports resolve.
Cache the raw document, not the object model. The parsed graph is often not cleanly serializable (recursive references, closures, namespace context). Cache the WSDL XML (and its imports) on disk or in your cache store, then re-parse quickly. A file-based cache keyed by URL hash is usually sufficient.
Integrate at the boundary of a generated client. In a phpro/soap-client-style setup, this package gives you introspection for the generator/config step; the actual runtime transport is another package. Keep the introspection layer isolated so a WSDL change only affects the build/generation code, not runtime request code.
Test against fixtures, including broken ones. Keep a minimal WSDL and a WSDL with imports in tests/fixtures/. Add a deliberately malformed file and assert the parser raises rather than silently producing a half-empty model.
wsdl:import location="service.wsdl" in a remote document is fetched relative to the parent URL (or the xml:base), not the current working directory. If you cache a remote WSDL to a local path, relative imports will break — either cache the whole tree preserving paths, or use a custom import resolver/loader.fromUri call and log the effective URL and status. Consider injecting a PSR-18/PSR-17 client if the library exposes a loader extension point — otherwise proxy through your own fetcher and pass a string.<xsd:schema> and <xsd:import> handling sits elsewhere in the ecosystem. If you need element/type information for message payloads, don't expect this package alone to give you deep schema validation.type reference is a QName, so match by resolved namespace + local name, not by raw string. Don't string-compare targetNamespace values with trailing slashes or case differences.soapAction, use document/literal in unusual combos, or define ports without bindings. Write defensive iteration and log skipped nodes rather than letting a null propagate.xmllint --noout file.wsdl catches well-formedness issues before you blame the parser.composer.json, and re-run your fixture-based tests on upgrade.tests/fixtures/ (sanitized) to catch regressions that matter.How can I help you explore Laravel packages today?