Closed staabm closed 8 years ago
Eating our own dogfood I already got something which works for the basic use-case:
#!/usr/bin/env php
<?php
use Sabre\Xml\Reader;
require __DIR__ .'/../../../../../vendor/autoload.php';
$srcFolder = $argv[1];
if (!is_readable($srcFolder)) {
throw new Exception('folder "'. $srcFolder .'" is not readable');
}
$reader = new Reader();
$reader->elementMap = array (
'{http://www.w3.org/2001/XMLSchema}element' => function(Reader $reader) {
$element = new XsdElement();
if ($name = $reader->getAttribute('name')) {
$element->name = $name;
}
if ($type = $reader->getAttribute('type')) {
$element->type = $type;
}
$children = $reader->parseInnerTree();
if (is_array($children)) {
foreach($children as $child) {
if ($child['value'] instanceof XsdComplexType) {
$element->complexType = $child['value'];
}
}
}
return $element;
},
'{http://www.w3.org/2001/XMLSchema}complexType' => function(Reader $reader) {
$complexType = new XsdComplexType();
if ($name = $reader->getAttribute('name')) {
$complexType->name = $name;
}
$children = $reader->parseInnerTree();
foreach($children as $child) {
if ($child['value'] instanceof XsdSequence) {
$complexType->sequence = $child['value'];
}
}
return $complexType;
},
'{http://www.w3.org/2001/XMLSchema}sequence' => function(Reader $reader) {
$sequence = new XsdSequence();
$children = $reader->parseInnerTree();
foreach($children as $child) {
if ($child['value'] instanceof XsdElement) {
$sequence->elements[] = $child['value'];
}
}
return $sequence;
}
);
$files = rglob($srcFolder. '/*.xsd');
foreach($files as $xsd) {
// FIXME: filter a fixed XSD for now
if (strpos($xsd, 'order_response.xsd') === false) continue;
echo $xsd."\n";
$reader->xml(file_get_contents($xsd));
$tree = $reader->parse();
print_r($tree);
}
// defines "instances" which actually can be used in XML files
class XsdElement{
/**
* @var string
*/
public $name;
/**
* @var string
*/
public $type;
/**
* @var XsdComplexType|null
*/
public $complexType;
}
// defines a type, meant for re-use across the XSD
class XsdComplexType {
/**
* @var string
*/
public $name;
/**
* @var XsdSequence
*/
public $sequence;
}
// defines a list or properties with a fixed order
class XsdSequence {
/**
* @var XsdElement[]
*/
public $elements = array();
}
class ValueObjectClass {
/**
* @var string
*/
public $name;
/**
* @var string
*/
public $namespace;
/**
* property-name => property-type
*
* @var array
*/
public $properties = array();
}
// stolen from http://www.westhost.com/contest/php/function/rglob/123
function rglob($pattern='*', $flags = 0, $path=false)
{
if (!$path) { $path=dirname($pattern).DIRECTORY_SEPARATOR; }
$pattern=basename($pattern);
$paths=glob($path.'*', GLOB_MARK|GLOB_ONLYDIR|GLOB_NOSORT);
$files=glob($path.$pattern, $flags);
foreach ($paths as $path) {
$files=array_merge($files,rglob($pattern, $flags, $path));
}
return $files;
}
which produces
./generator.php entities/
entities/order_response.xsd
Array
(
[name] => {http://www.w3.org/2001/XMLSchema}schema
[value] => Array
(
[0] => Array
(
[name] => {http://www.w3.org/2001/XMLSchema}element
[value] => XsdElement Object
(
[name] => order_response
[type] =>
[complexType] => XsdComplexType Object
(
[name] =>
[sequence] => XsdSequence Object
(
[elements] => Array
(
[0] => XsdElement Object
(
[name] => request_id
[type] => xs:string
[complexType] =>
)
[1] => XsdElement Object
(
[name] => order_id
[type] => xs:string
[complexType] =>
)
[2] => XsdElement Object
(
[name] => status
[type] => xs:string
[complexType] =>
)
[3] => XsdElement Object
(
[name] => version
[type] => xs:int
[complexType] =>
)
[4] => XsdElement Object
(
[name] => success
[type] => xs:boolean
[complexType] =>
)
[5] => XsdElement Object
(
[name] => error_detail
[type] => errorDetailType
[complexType] =>
)
)
)
)
)
[attributes] => Array
(
[name] => order_response
)
)
[1] => Array
(
[name] => {http://www.w3.org/2001/XMLSchema}complexType
[value] => XsdComplexType Object
(
[name] => errorDetailType
[sequence] => XsdSequence Object
(
[elements] => Array
(
[0] => XsdElement Object
(
[name] => error_code
[type] => xs:string
[complexType] =>
)
[1] => XsdElement Object
(
[name] => error_message
[type] => xs:string
[complexType] =>
)
)
)
)
[attributes] => Array
(
[name] => errorDetailType
)
)
)
[attributes] => Array
(
[targetNamespace] => http://clxERP/order
[elementFormDefault] => qualified
)
)
for this XSD
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://clxERP/order"
targetNamespace="http://clxERP/order"
elementFormDefault="qualified">
<xs:element name="order_response">
<xs:complexType>
<xs:sequence>
<xs:element name="request_id" type="xs:string" />
<xs:element name="order_id" type="xs:string" />
<xs:element name="status" type="xs:string" />
<xs:element name="version" type="xs:int" />
<xs:element name="success" type="xs:boolean" />
<xs:element name="error_detail" type="errorDetailType" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="errorDetailType">
<xs:sequence>
<xs:element name="error_code" type="xs:string"/>
<xs:element name="error_message" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
one problem right now: the property error_detail
references another complexType
within the xsd instead of a "scalar" type like xs:string
. I am not yet sure how to parse a xs:element
's type
attribute and determine whether it is pointing to another complexType
or it is just a primitive.
any idea on that?
btw: playing with sabre/xml in more use-cases gave me the impression I am using the right lib. It was really easy to get to the point where I am right now. :rocket: :tada:
I am a little worried honestly that the amount of xml documents we can actually parse with this is too low. scalars work fine, but I imagine that most are simply more complex than that.
I don't feel super comfortable adding a potentially complicated feature that people will not be able to use as soon as they go beyond super-simple key-value.
One thing I would really like though, is to create separate composer packages that ship standard serializer/deserializers for popular xml formats... such as xmlschema (sabre/xml-xmlschema
), and atom (sabre/xml-atom
) and eventually a separate package for all DAV (de-)serializers, etc... I think those could be super useful and it would create a bit of an ecosystem of xml parsing packages.
However, if you think I'm wrong, I'd also love to hear it... I'm also coming in here from a perspective that's "I don't think I can use this feature myself, so I think others won't either".
I am not yet sure if this is generic enough for use in the lib. i will finish the command nevertheless for my "specific" use case, so it wont be lost or integrated later on, after we used it in several scenarios.
I agree that having some kind of serializer libs, which are more or less, lib independent could create a eco-system for itself.
I will finish my command tomorrow and maybe come back to this after getting more in touch with it. Thanks for your opinion.
Since I started using the new value-object feature for our contract first webservices I get into the situation where I have to manually define a lot of php value objects, which map 1:1 via the new feature a corresponding XSD.
What do you think about adding a new cli command to create/generate php files from a xsd, in a form that they are 1:1 consumable using the new feature?
I already googled for an hour but wasnt able to find something suitable which isnt part of a huge component or library.
I thought about a very small script, in which you feed in a XSD and creates classes from xsd
complexType
s ,interprets min/maxOccurs and some basic types likestring
,long
, etc. and adds some docblocks for metainfos which we cannot typehint in php5.5+at best we would have a lib which does this things already, but I could not find one right now.