Simple Types with Enumeration Aren't Created
Opened this issue · 11 comments
So, I'm using your library and overriding it a bit to customize my output, but I've noticed that it doesn't create simpleTypes, which is rather frustrating. For example, I'm using it on the Microsoft EWS xsd and wsdl files, and they have this definition
<xs:simpleType name="DateTimePrecisionType">
<xs:annotation>
<xs:documentation>Precision for returned DateTime values</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:enumeration value="Seconds" />
<xs:enumeration value="Milliseconds" />
</xs:restriction>
</xs:simpleType>
<xs:element name="DateTimePrecision" type="t:DateTimePrecisionType" />
And with this, the DateTimePrecision class will be created, it'll set to extend from DateTimePrecisionType, but DateTimePrecisionType will never exist, which causes an error
DateTimePrecisionType
in php does not make sense since you can not limit the DateTime
granularity.
Same thing for all the php base types
Because of this, restrictions/extension for all simple types are not generated.
The problem it that primitives on php are not objects. so if you want a string with a max lenght = 20, you can not do something as:
class MyString extends String{
function __construct($value){
parent::__construct(substr($value, 20));
}
}
I'm using this to generate classes based on the WSDL from Microsoft. I only mentioned this because this is one of the problems that I ran in to. A class DateTimePrecision extends DateTimePrecisionType
class was created without a DateTimePrecisionType
class existing, so I had to create a workaround to make that exist
type aliases
are ment to this,
--alias-map='http://www.somens/;DateTimePrecisionType;DateTime'
@goetas: Just because an XML type is based on a primitive certainly doesn't mean that it should be mapped onto a PHP primitive. On the contrary, better data typing would arise from mapping such XML simpleType
s to their own PHP classes (extending \stdClass
)—any unimplementable restriction details could simply be included amongst the generated PHPDoc comments. For example:
/**
* Precision for returned DateTime values
*
* <xs:restriction base="xs:string">
* <xs:enumeration value="Seconds" />
* <xs:enumeration value="Milliseconds" />
* </xs:restriction>
*/
class DateTimePrecisionType
{
// etc
Of course, there are also ways that such a generated class could fully implement the restrictions at runtime—e.g. in the setter method:
public function setValue($value)
{
// <xs:restriction base="xs:string">
if (!is_string($value)) {
throw new \InvalidArgumentException();
}
// <xs:enumeration value="Seconds" />
// <xs:enumeration value="Milliseconds" />
if (!in_array($value, ['Seconds', 'Milliseconds'])) {
throw new \InvalidArgumentException();
}
$this->value = $value;
return $this;
}
However I do recognise that supporting every conceivable xs:restriction
might not be so easy.
A class DateTimePrecision extends DateTimePrecisionType class was created without a DateTimePrecisionType class existing,
this sounds like a bug. can you post the xsd that you tried to convert?
@eggyal to be honest I already tried this approach, but i removed it because was more problematic than useful.
If you checkout the repo and go back to something older than c270a47 you will have much more classes.
Your approach idea works really well with datetime objects (this because we have a DateTime
object.
But with strings/integers/floats is much worst.
Suppose:
<complexType name="miniSum">
<sequence>
<element name="operand1" type="tns:MiniInteger"/>
<element name="operand2" type="tns:MiniInteger"/>
</sequence>
</complexType>
<simpleType name="MiniInteger">
<restriction base="integer">
<maxInclusive value="5"/>
</restriction>
</simpleType>
The current version will generate:
class MinSum
{
protected $operand1;
protected $operand2;
public function getOperand1()
{
return $this->operand1;
}
public function setOperand1($operand1)
{
$this->operand1 = $operand1;
return $this;
}
public function getOperand2()
{
return $this->operand2;
}
public function setOperand2($operand2)
{
$this->operand2 = $operand2;
return $this;
}
}
and doing something as this is perfectly legal.
$sum = new MinSum();
$sum->setOperand1(2);
$sum->setOperand2(5);
$res = $sum->setOperand1() * $sum->setOperand2(); // 10
Of course, since MiniInteger
restrictions are ignored, you can do something as this that will produce a wrong xml serialization.
$sum = new MinSum();
$sum->setOperand1(20000000000000);
$sum->setOperand2(500000000000000000);
If I start generating wrappers for primitive classes, things becomes dirtier. The generated classes has to be something as:
class MinInteger
{
protected $value;
public function __construct($value)
{
if ($value>5) {
throw new OutOfBoundsException("The max value for MinInteger is 5");
}
$this->value = $value;
}
public function __toString()
{
// toString MUST return a string
return strval($this->value);
}
}
class MinSum
{
protected $operand1;
protected $operand2;
public function getOperand1()
{
return $this->operand1;
}
public function setOperand1(MinInteger $operand1)
{
$this->operand1 = $operand1;
return $this;
}
public function getOperand2()
{
return $this->operand2;
}
public function setOperand2(MinInteger $operand2)
{
$this->operand2 = $operand2;
return $this;
}
}
And if I want to use it:
$sum = new MinSum();
$sum->setOperand1(new MinInteger(2));
$sum->setOperand2(new MinInteger(5));
In php we are not yet used to use objects to represent primitive types. and a lot of php built in functions are not able to handle them properly.
But this will also generate an exception:
$res = $sum->getOperand1() * $sum->getOperand2();
// Object of class MinInteger could not be converted to int
foreach (range(1, $sum->getOperand1()) as $i){ // raises again Object of class MinInteger could not be converted to int
// bla bla bla
}
Because of the not fully object oriented nature of php, is really hard implement some xsd features in a decent way. Using Java or C# thing will be much easier.
At the moment DateTime
is the only type that can be benefit of using this approach.
If you want to implement some restirctions on DateTime
derivates, i suggest you to create a custom type handler as done here.
You can throw error when serializing if the php's DateTime
does not respect the type limitations.
@goetas: Thank you for your detailed thoughts!
Personally, I find the second version "cleaner" insofar as the PHP type system will prevent something that isn't a tns:MiniInteger
being assigned to such a field. But I hear your concerns.
Nevertheless, perhaps one could still document the restrictions in the class:
class MinSum
{
/**
* <simpleType name="MiniInteger">
* <restriction base="integer">
* <maxInclusive value="5"/>
* </restriction>
* </simpleType>
*/
protected $operand1;
/**
* <simpleType name="MiniInteger">
* <restriction base="integer">
* <maxInclusive value="5"/>
* </restriction>
* </simpleType>
*/
protected $operand2;
// ...
Perhaps even create data validators, so that you can continue using primitive types but still provide some runtime type-checking in a sane way that is reusable across all instances of that type:
abstract final class DataValidators
{
/**
* Checks whether a value is a valid MiniInteger
* @param mixed $value The value to be checked
* @return boolean Whether the value is a valid MiniInteger
*/
public static function isMiniInteger($value)
{
$isValid = false;
// <restriction base="integer">
if (is_integer($value)) {
// <maxInclusive value="5"/>
if ($value <= 5) {
$isValid = true;
}
}
return $isValid;
}
}
class MiniSum
{
/**
* @var integer
*/
protected $operand1;
public function setOperand1($operand1)
{
if (!DataValidators::isMiniInteger($operand1))
throw new \InvalidArgumentException();
$this->operand1 = $operand1;
return $this;
}
// ...
Hi. Here is a link to the xsd and the line for the xsd. It's what's supplied by the Microsoft EWS server. At the top is what was generated originally. This is the documentation for the element. Essentially it's just sending a constant to the EWS server to tell it what format of response it wanted back
@Garethp the "validatior" approach is very interesting. I need sometime to implement something as proposed.
Regarding the "bug" i will take a look as soon as possible
I have same problem. Generator have generated classes which extend non existent classes for simple types.
It works fine with using allias parameter. But it will be better if it throws some warning about missing alliases for simple types.