├── .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 | [](https://travis-ci.org/goetas-webservices/xsd2php-runtime)
4 | [](https://scrutinizer-ci.com/g/goetas-webservices/xsd2php-runtime/?branch=master)
5 | [](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 |
--------------------------------------------------------------------------------