├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── Jms │ └── Handler │ ├── BaseTypesHandler.php │ └── XmlSchemaDateHandler.php └── tests ├── XmlSchemaDateHandlerDeserializationTest.php └── XmlSchemaDateHandlerSerializationTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.buildpath 2 | /.project 3 | /.settings 4 | /.idea 5 | composer.lock 6 | vendor/ 7 | /.phpunit.result.cache 8 | 9 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | tools: 2 | external_code_coverage: 3 | timeout: 600 4 | checks: 5 | php: 6 | code_rating: true 7 | duplication: true 8 | filter: 9 | paths: 10 | - src/* 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | sudo: false 3 | cache: 4 | directories: 5 | - vendor 6 | - $HOME/.composer/cache 7 | php: 8 | - 7.1 9 | - 7.2 10 | - 7.3 11 | - 7.4 12 | - 8.0 13 | 14 | matrix: 15 | include: 16 | - php: 7.1 17 | dist: trusty 18 | env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' 19 | 20 | before_script: 21 | - if [[ $TRAVIS_PHP_VERSION = '7.1' ]]; then PHPUNIT_FLAGS="--coverage-clover=coverage.clover"; else PHPUNIT_FLAGS=""; fi 22 | - if [[ $TRAVIS_PHP_VERSION != '7.1' ]]; then phpenv config-rm xdebug.ini; fi 23 | - composer self-update 24 | - composer update $COMPOSER_FLAGS 25 | 26 | script: vendor/phpunit/phpunit/phpunit $PHPUNIT_FLAGS 27 | 28 | after_script: 29 | - wget https://scrutinizer-ci.com/ocular.phar 30 | - if [[ $TRAVIS_PHP_VERSION = '7.1' ]]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Asmir Mustafic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xsd2php-runtime 2 | 3 | [![Build Status](https://travis-ci.org/goetas-webservices/xsd2php-runtime.svg?branch=master)](https://travis-ci.org/goetas-webservices/xsd2php-runtime) 4 | [![Code Coverage](https://scrutinizer-ci.com/g/goetas-webservices/xsd2php-runtime/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/goetas-webservices/xsd2php-runtime/?branch=master) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/goetas-webservices/xsd2php-runtime/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/goetas-webservices/xsd2php-runtime/?branch=master) 6 | 7 | 8 | Runtime libs for https://github.com/goetas-webservices/xsd2php 9 | 10 | ## Note 11 | 12 | The code in this project is provided under the 13 | [MIT](https://opensource.org/licenses/MIT) license. 14 | For professional support 15 | contact [goetas@gmail.com](mailto:goetas@gmail.com) 16 | or visit [https://www.goetas.com](https://www.goetas.com) 17 | 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goetas-webservices/xsd2php-runtime", 3 | "description": "Convert XSD (XML Schema) definitions into PHP classes", 4 | "type": "library", 5 | "authors": [ 6 | { 7 | "name": "Asmir Mustafic" 8 | } 9 | ], 10 | "keywords": [ 11 | "converter", 12 | "xml", 13 | "xsd", 14 | "php", 15 | "jms", 16 | "serializer" 17 | ], 18 | "license": "MIT", 19 | "require": { 20 | "php": ">=7.1", 21 | "jms/serializer": "^1.2|^2.0|^3.0", 22 | "symfony/yaml": "^2.2|^3.0|^4.0|^5.0|^6.0|^7.0" 23 | }, 24 | "conflict": { 25 | "jms/serializer": "1.4.1|1.6.1|1.6.2" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^7.0|^8.0|^9.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "GoetasWebservices\\Xsd\\XsdToPhpRuntime\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "GoetasWebservices\\Xsd\\XsdToPhpRuntime\\Tests\\": "tests/" 38 | } 39 | }, 40 | "extra": { 41 | "branch-alias": { 42 | "dev-master": "0.2-dev" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | ./tests 22 | 23 | 24 | 25 | 26 | 27 | src 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Jms/Handler/BaseTypesHandler.php: -------------------------------------------------------------------------------- 1 | GraphNavigator::DIRECTION_SERIALIZATION, 21 | 'format' => 'xml', 22 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\Jms\SimpleListOf', 23 | 'method' => 'simpleListOfToXml' 24 | ), 25 | array( 26 | 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, 27 | 'format' => 'xml', 28 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\Jms\SimpleListOf', 29 | 'method' => 'simpleListOfFromXML' 30 | ), 31 | array( 32 | 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, 33 | 'format' => 'xml', 34 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\Jms\Base64Encoded', 35 | 'method' => 'base64EncodedToXml' 36 | ), 37 | array( 38 | 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, 39 | 'format' => 'xml', 40 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\Jms\Base64Encoded', 41 | 'method' => 'base64EncodedFromXml' 42 | ) 43 | ); 44 | } 45 | public function base64EncodedToXml(XmlSerializationVisitor $visitor, $data, array $type, Context $context) 46 | { 47 | return $visitor->visitSimpleString(base64_encode($data), $type, $context); 48 | } 49 | 50 | public function base64EncodedFromXml(XmlDeserializationVisitor $visitor, $data, array $type) 51 | { 52 | $attributes = $data->attributes('xsi', true); 53 | if (isset($attributes['nil'][0]) && (string)$attributes['nil'][0] === 'true') { 54 | return null; 55 | } 56 | 57 | return base64_decode((string)$data); 58 | } 59 | 60 | public function simpleListOfToXml(XmlSerializationVisitor $visitor, $object, array $type, Context $context) 61 | { 62 | 63 | $newType = array( 64 | 'name' => $type["params"][0]["name"], 65 | 'params' => array() 66 | ); 67 | 68 | $navigator = $context->getNavigator(); 69 | $ret = array(); 70 | foreach ($object as $v) { 71 | $ret[] = $navigator->accept($v, $newType, $context)->data; 72 | } 73 | 74 | return $visitor->getDocument()->createTextNode(implode(" ", $ret)); 75 | } 76 | 77 | public function simpleListOfFromXml(XmlDeserializationVisitor $visitor, $node, array $type, Context $context) 78 | { 79 | $newType = array( 80 | 'name' => $type["params"][0]["name"], 81 | 'params' => array() 82 | ); 83 | $ret = array(); 84 | $navigator = $context->getNavigator(); 85 | foreach (explode(" ", (string)$node) as $v) { 86 | $ret[] = $navigator->accept($v, $newType, $context); 87 | } 88 | return $ret; 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/Jms/Handler/XmlSchemaDateHandler.php: -------------------------------------------------------------------------------- 1 | GraphNavigator::DIRECTION_DESERIALIZATION, 24 | 'format' => 'xml', 25 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\XMLSchema\Date', 26 | 'method' => 'deserializeDate' 27 | ), 28 | array( 29 | 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, 30 | 'format' => 'xml', 31 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\XMLSchema\Date', 32 | 'method' => 'serializeDate' 33 | ), 34 | array( 35 | 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, 36 | 'format' => 'xml', 37 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\XMLSchema\DateTime', 38 | 'method' => 'deserializeDateTime' 39 | ), 40 | array( 41 | 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, 42 | 'format' => 'xml', 43 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\XMLSchema\DateTime', 44 | 'method' => 'serializeDateTime' 45 | ), 46 | array( 47 | 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, 48 | 'format' => 'xml', 49 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\XMLSchema\Time', 50 | 'method' => 'deserializeTime' 51 | ), 52 | array( 53 | 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, 54 | 'format' => 'xml', 55 | 'type' => 'GoetasWebservices\Xsd\XsdToPhp\XMLSchema\Time', 56 | 'method' => 'serializeTime' 57 | ), 58 | array( 59 | 'type' => 'DateInterval', 60 | 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, 61 | 'format' => 'xml', 62 | 'method' => 'deserializeDateIntervalXml', 63 | ), 64 | ); 65 | } 66 | 67 | public function __construct($defaultTimezone = 'UTC') 68 | { 69 | $this->defaultTimezone = new \DateTimeZone($defaultTimezone); 70 | 71 | } 72 | 73 | public function deserializeDateIntervalXml(XmlDeserializationVisitor $visitor, $data, array $type){ 74 | $attributes = $data->attributes('xsi', true); 75 | if (isset($attributes['nil'][0]) && (string) $attributes['nil'][0] === 'true') { 76 | return null; 77 | } 78 | return $this->createDateInterval((string)$data); 79 | } 80 | 81 | public function serializeDate(XmlSerializationVisitor $visitor, \DateTime $date, array $type, Context $context) 82 | { 83 | 84 | $v = $date->format('Y-m-d'); 85 | 86 | return $visitor->visitSimpleString($v, $type, $context); 87 | } 88 | 89 | public function deserializeDate(XmlDeserializationVisitor $visitor, $data, array $type) 90 | { 91 | $attributes = $data->attributes('xsi', true); 92 | if (isset($attributes['nil'][0]) && (string)$attributes['nil'][0] === 'true') { 93 | return null; 94 | } 95 | if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})(Z|([+-]\d{2}:\d{2}))?$/', $data)) { 96 | throw new RuntimeException(sprintf('Invalid date "%s", expected valid XML Schema date string.', $data)); 97 | } 98 | 99 | return $this->parseDateTime($data, $type); 100 | } 101 | 102 | public function serializeDateTime(XmlSerializationVisitor $visitor, \DateTime $date, array $type, Context $context) 103 | { 104 | 105 | $v = $date->format(\DateTime::W3C); 106 | 107 | return $visitor->visitSimpleString($v, $type, $context); 108 | } 109 | 110 | public function deserializeDateTime(XmlDeserializationVisitor $visitor, $data, array $type) 111 | { 112 | $attributes = $data->attributes('xsi', true); 113 | if (isset($attributes['nil'][0]) && (string)$attributes['nil'][0] === 'true') { 114 | return null; 115 | } 116 | 117 | return $this->parseDateTime($data, $type); 118 | 119 | } 120 | 121 | public function serializeTime(XmlSerializationVisitor $visitor, \DateTime $date, array $type, Context $context) 122 | { 123 | $v = $date->format('H:i:s'); 124 | if ($date->getTimezone()->getOffset($date) !== $this->defaultTimezone->getOffset($date)) { 125 | $v .= $date->format('P'); 126 | } 127 | return $visitor->visitSimpleString($v, $type, $context); 128 | } 129 | 130 | public function deserializeTime(XmlDeserializationVisitor $visitor, $data, array $type) 131 | { 132 | $attributes = $data->attributes('xsi', true); 133 | if (isset($attributes['nil'][0]) && (string)$attributes['nil'][0] === 'true') { 134 | return null; 135 | } 136 | 137 | $data = (string)$data; 138 | 139 | return new \DateTime($data, $this->defaultTimezone); 140 | } 141 | 142 | private function parseDateTime($data, array $type) 143 | { 144 | $timezone = isset($type['params'][1]) ? new \DateTimeZone($type['params'][1]) : $this->defaultTimezone; 145 | $datetime = new \DateTime((string)$data, $timezone); 146 | if (false === $datetime) { 147 | throw new RuntimeException(sprintf('Invalid datetime "%s", expected valid XML Schema dateTime string.', $data)); 148 | } 149 | 150 | return $datetime; 151 | } 152 | 153 | private function createDateInterval($interval){ 154 | $f = 0.0; 155 | if (preg_match('~\.\d+~',$interval,$match)) { 156 | $interval = str_replace($match[0], "", $interval); 157 | $f = (float)$match[0]; 158 | } 159 | $di = new \DateInterval($interval); 160 | // milliseconds are only available from >=7.1 161 | if(isset($di->f)){ 162 | $di->f= $f; 163 | } 164 | 165 | return $di; 166 | } 167 | } 168 | 169 | -------------------------------------------------------------------------------- /tests/XmlSchemaDateHandlerDeserializationTest.php: -------------------------------------------------------------------------------- 1 | handler = new XmlSchemaDateHandler(); 38 | $this->context = DeserializationContext::create(); 39 | $naming = new IdenticalPropertyNamingStrategy(); 40 | 41 | 42 | 43 | $dispatcher = new EventDispatcher(); 44 | $handlerRegistry= new HandlerRegistry(); 45 | $cons = new UnserializeObjectConstructor(); 46 | 47 | $navigator = class_exists('JMS\Serializer\GraphNavigator\DeserializationGraphNavigator') 48 | ? $this->initJmsv2($naming, $handlerRegistry, $cons, $dispatcher) 49 | : $this->initJmsv1($naming, $handlerRegistry, $cons, $dispatcher) 50 | ; 51 | $this->visitor->setNavigator($navigator); 52 | } 53 | 54 | private function initJmsv2($naming, $handlerRegistry, $cons, $dispatcher) 55 | { 56 | $accessor = new DefaultAccessorStrategy(); 57 | $this->visitor = new XmlDeserializationVisitor(); 58 | $metadataFactory = new MetadataFactory(new AnnotationDriver(new AnnotationReader(), $naming)); 59 | return new GraphNavigator\DeserializationGraphNavigator($metadataFactory, $handlerRegistry, $cons, $accessor, $dispatcher); 60 | } 61 | 62 | private function initJmsv1($naming, $handlerRegistry, $cons, $dispatcher) 63 | { 64 | $this->visitor = new XmlDeserializationVisitor($naming); 65 | $metadataFactory = new MetadataFactory(new AnnotationDriver(new AnnotationReader())); 66 | return new GraphNavigator($metadataFactory, $handlerRegistry, $cons, $dispatcher); 67 | } 68 | 69 | /** 70 | * @dataProvider getDeserializeDate 71 | * @param string $date 72 | * @param \DateTime $expected 73 | */ 74 | public function testDeserializeDate($date, \DateTime $expected) 75 | { 76 | $element = new \SimpleXMLElement("$date"); 77 | $deserialized = $this->handler->deserializeDate($this->visitor, $element, [], $this->context); 78 | $this->assertEquals($expected, $deserialized); 79 | } 80 | 81 | /** 82 | * @dataProvider getDeserializeDateInterval 83 | * @param string $dateInterval 84 | * @param \DateTime $expected 85 | */ 86 | public function testDeserializeDateInterval($dateInterval, $expected) 87 | { 88 | $element = new \SimpleXMLElement("$dateInterval"); 89 | $deserialized = $this->handler->deserializeDateIntervalXml($this->visitor, $element, [], $this->context); 90 | if (isset($deserialized->f)) { 91 | $this->assertEquals($expected['f'], $deserialized->f); 92 | } 93 | $this->assertEquals($expected['s'], $deserialized->s); 94 | } 95 | 96 | public function getDeserializeDateInterval() 97 | { 98 | return [ 99 | ['P0Y0M0DT3H5M7.520S', ['s' => 7, 'f' => 0.52]], 100 | ['P0Y0M0DT3H5M7S', ['s' => 7, 'f' => 0]] 101 | ]; 102 | } 103 | 104 | public function getDeserializeDate() 105 | { 106 | return [ 107 | ['2015-01-01', new \DateTime('2015-01-01')], 108 | ['2015-01-01Z', new \DateTime('2015-01-01', new \DateTimeZone("UTC"))], 109 | ['2015-01-01+06:00', new \DateTime('2015-01-01', new \DateTimeZone("+06:00"))], 110 | ['2015-01-01-20:00', new \DateTime('2015-01-01', new \DateTimeZone("-20:00"))], 111 | ]; 112 | } 113 | 114 | public function testDeserializeInvalidDate() 115 | { 116 | $this->expectException(\RuntimeException::class); 117 | $element = new \SimpleXMLElement("2015-01-01T"); 118 | $this->handler->deserializeDate($this->visitor, $element, [], $this->context); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/XmlSchemaDateHandlerSerializationTest.php: -------------------------------------------------------------------------------- 1 | handler = new XmlSchemaDateHandler(); 37 | $this->context = SerializationContext::create(); 38 | 39 | $naming = new IdenticalPropertyNamingStrategy(); 40 | $cons = new UnserializeObjectConstructor(); 41 | 42 | $dispatcher = new EventDispatcher(); 43 | $handlerRegistry= new HandlerRegistry(); 44 | 45 | $navigator = class_exists('JMS\Serializer\GraphNavigator\DeserializationGraphNavigator') 46 | ? $this->initJmsv2($naming, $handlerRegistry, $cons, $dispatcher) 47 | : $this->initJmsv1($naming, $handlerRegistry, $cons, $dispatcher) 48 | ; 49 | $this->visitor->setNavigator($navigator); 50 | } 51 | 52 | private function initJmsv2($naming, $handlerRegistry, $cons, $dispatcher) 53 | { 54 | $accessor = new DefaultAccessorStrategy(); 55 | $this->visitor = new XmlSerializationVisitor(); 56 | $metadataFactory = new MetadataFactory(new AnnotationDriver(new AnnotationReader(), $naming)); 57 | return new GraphNavigator\SerializationGraphNavigator($metadataFactory, $handlerRegistry, $accessor, $dispatcher); 58 | } 59 | 60 | private function initJmsv1($naming, $handlerRegistry, $cons, $dispatcher) 61 | { 62 | $this->visitor = new XmlSerializationVisitor($naming); 63 | $metadataFactory = new MetadataFactory(new AnnotationDriver(new AnnotationReader())); 64 | return new GraphNavigator($metadataFactory, $handlerRegistry, $cons, $dispatcher); 65 | } 66 | 67 | /** 68 | * @dataProvider getSerializeDateTime 69 | * @param \DateTime $date 70 | */ 71 | public function testSerializeDateTime(\DateTime $date, $expected) 72 | { 73 | $ret = $this->handler->serializeDateTime($this->visitor, $date, [], $this->context); 74 | $actual = $ret ? $ret->nodeValue : $this->visitor->getCurrentNode()->nodeValue; 75 | $this->assertEquals($expected, $actual); 76 | } 77 | 78 | public function getSerializeDateTime() 79 | { 80 | return [ 81 | [new \DateTime('2015-01-01 12:00+00:00'), '2015-01-01T12:00:00+00:00'], 82 | [new \DateTime('2015-01-01 12:00:56+00:00'), '2015-01-01T12:00:56+00:00'], 83 | [new \DateTime('2015-01-01 12:00:56+00:00'), '2015-01-01T12:00:56+00:00'], 84 | [new \DateTime('2015-01-01 12:00:56+20:00'), '2015-01-01T12:00:56+20:00'], 85 | [new \DateTime('2015-01-01 12:00:56', new \DateTimeZone("Europe/London")), '2015-01-01T12:00:56+00:00'], 86 | [new \DateTime('2015-01-01 12:00:56+00:00', new \DateTimeZone("Europe/London")), '2015-01-01T12:00:56+00:00'], 87 | [new \DateTime('2015-01-01 12:00:56', new \DateTimeZone("Europe/Rome")), '2015-01-01T12:00:56+01:00'], 88 | ]; 89 | } 90 | 91 | /** 92 | * @dataProvider getSerializeDate 93 | * @param \DateTime $date 94 | * @param string $expected 95 | */ 96 | public function testSerializeDate(\DateTime $date, $expected) 97 | { 98 | $ret = $this->handler->serializeDate($this->visitor, $date, [], $this->context); 99 | 100 | $actual = $ret ? $ret->nodeValue : $this->visitor->getCurrentNode()->nodeValue; 101 | $this->assertEquals($expected, $actual); 102 | } 103 | 104 | public function getSerializeDate() 105 | { 106 | return [ 107 | [new \DateTime('2015-01-01 12:00'), '2015-01-01'], 108 | [new \DateTime('2015-01-01 12:00:56'), '2015-01-01'], 109 | [new \DateTime('2015-01-01 12:00:56+00:00'), '2015-01-01'], 110 | [new \DateTime('2015-01-01 12:00:56+20:00'), '2015-01-01'], 111 | [new \DateTime('2015-01-01 12:00:56', new \DateTimeZone("Europe/London")), '2015-01-01'], 112 | ]; 113 | } 114 | } 115 | --------------------------------------------------------------------------------