goetas-webservices/xsd2php-runtime
Runtime support for code generated by goetas-webservices/xsd2php. Provides the base classes and helpers needed to use PHP classes generated from XML Schema definitions, simplifying integration and serialization in PHP projects.
This package is a runtime library — it contains no generator, no console command, and no Laravel service provider. It exists so that PHP classes produced by goetas-webservices/xsd2php can be autoloaded and (de)serialized in production. Your first steps are therefore about splitting generator from runtime.
Install the runtime in production, the generator in dev:
composer require goetas-webservices/xsd2php-runtime
composer require --dev goetas-webservices/xsd2php
Keep them roughly in step — generated code from a newer generator may reference handler/base types that an older runtime does not ship (you'll see Class not found or "no handler for type" errors).
Look first at what the generator actually emitted, not at this package. Open a couple of generated classes and note:
xsd2php config),@JMS\Type(...) / @JMS\XmlNamespace annotations appear on properties — these tell you which runtime handlers you must register,\DateTime, \DateInterval, SimpleXMLElement, or interface references in use statements. Those are the runtime's surface area.Generate one small schema to prove the pipeline end to end. Use the xsd2php console binary (or the Xsd2Php class programmatically) with a YAML/array config mapping XSD namespaces to PHP namespaces:
# xsd2php.yaml (build-time only)
my_schema:
xsd: /path/to/schema.xsd
php: App\Generated\MySchema
Then composer dump-autoload after pointing PSR-4 at the output directory. Nothing in this package wires autoloading for you.
First real use case: parse an inbound XML document into typed objects.
use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\Handler\HandlerRegistryInterface;
use GoetasWebservices\Xsd\XsdToPhpRuntime\Jms\Handler\BaseTypesHandler;
use GoetasWebservices\Xsd\XsdToPhpRuntime\Jms\Handler\XmlSchemaDateHandler;
$serializer = SerializerBuilder::create()
->configureHandlers(function (HandlerRegistryInterface $handlers) {
$handlers->registerSubscribingHandler(new BaseTypesHandler());
$handlers->registerSubscribingHandler(new XmlSchemaDateHandler());
})
->setCacheDir(storage_path('framework/cache/jms'))
->build();
$order = $serializer->deserialize($xmlString, App\Generated\MySchema\OrderType::class, 'xml');
The XSD-specific types (xs:date, xs:dateTime, xs:time, xs:duration, binary types) are handled by those handlers, not by JMS's defaults. addDefaultHandlers(true) alone is not sufficient.
Build-step generation, committed or cached output. Treat generated classes like compiled assets. A typical Laravel setup:
app/Generated/ (or generated/) as the target, registered in composer.json under autoload.psr-4.xsd:generate that shells out to vendor/bin/xsd2php (or calls the generator class directly) so developers and CI use one code path.composer dump-autoload, and fail the CI build if regeneration produces a diff — that catches schemas that changed without committed code being updated.Bind a single configured serializer in a service provider. JMS's SerializerBuilder and metadata parsing are expensive; build once and share it.
$this->app->singleton(SerializerInterface::class, function () {
return SerializerBuilder::create()
->configureHandlers(function (HandlerRegistryInterface $h) {
$h->registerSubscribingHandler(new BaseTypesHandler());
$h->registerSubscribingHandler(new XmlSchemaDateHandler());
})
->setCacheDir(storage_path('framework/cache/jms'))
->build();
});
Point setCacheDir at a writable path and clear it on deploy (or key it by release) — stale JMS metadata after a regeneration is the most common "why is this field null" bug.
Wrap the serializer behind a domain gateway. Do not scatter App\Generated\... class names through controllers. A small service per integration (InvoiceXmlGateway, ShippingStatusMapper) that accepts a string, validates, deserializes, and returns your own DTOs keeps the generated namespace an implementation detail you can regenerate freely.
Validate against the XSD before deserializing. JMS will happily produce a half-populated object from garbage XML. A DOMDocument::schemaValidate() pass turns "silently wrong data" into a clear exception with line numbers.
Convert typed objects to array/JSON for your API layer. JMS can serialize to json from the same objects, which makes generated models a convenient internal representation:
$json = $serializer->serialize($order, 'json', SerializationContext::create()->setSerializeNull(false));
Round-trip tests. Commit a few fixture XML files under tests/fixtures/xsd/. Test deserialize -> serialize -> C14N and compare canonicalized XML. That single test catches handler misregistration, namespace drift, and date-format regressions better than anything else.
If you're doing SOAP, goetas-webservices/soap-client builds on this runtime and its handler classes; register your handlers on that client's serializer too, or date fields will arrive as raw strings.
xsd2php to require "just in case" — it pulls code-generation dependencies into production and can conflict on transitive packages.XmlSchemaDateHandler / BaseTypesHandler live under GoetasWebservices\Xsd\XsdToPhpRuntime\Jms\Handler\. Before upgrading, list vendor/goetas-webservices/xsd2php-runtime/src/Jms/Handler/ — a renamed or removed handler shows up as a runtime Type "... " is not registered error only on the specific fields that use it.ns2: is fine; a sender using a slightly different URI (or the unqualified default namespace) produces empty objects without an error. Log the raw payload on any "all fields null" result.minOccurs="0" and maxOccurs="unbounded" semantics. Optional repeated elements deserialize to arrays that may be null or empty; always guard before foreach. Optional scalars may be null even when you expected a default.xs:date and xs:dateTime are different generated types; timezone offsets and fractional seconds are preserved inconsistently. Normalize to UTC in your gateway layer, not in the generated classes.xs:any / xs:anyType / wildcards. These land as generic DOM/SimpleXMLElement content. Treat as untrusted — never evaluate or pass through to templates, and strip them if you only need a few known fields.libxml_disable_entity_loader(true) on older PHP, or the LIBXML_NONET flag) — Laravel gives you nothing here by default.SubscribingHandlerInterface implementation or a JMS event listener registered on your builder — subclassing generated classes is a dead end because they get overwritten on the next generation run.How can I help you explore Laravel packages today?