Maurits van der Schee 4 years ago
parent
commit
be3946696d

+ 125
- 1
api.php View File

@@ -7347,7 +7347,6 @@ namespace Tqdev\PhpCrudApi\Middleware {
7347 7347
     use Psr\Http\Message\ResponseInterface;
7348 7348
     use Psr\Http\Message\ServerRequestInterface;
7349 7349
     use Psr\Http\Server\RequestHandlerInterface;
7350
-    use Tqdev\PhpCrudApi\Controller\Responder;
7351 7350
     use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
7352 7351
     use Tqdev\PhpCrudApi\Record\ErrorCode;
7353 7352
     use Tqdev\PhpCrudApi\ResponseFactory;
@@ -8517,6 +8516,122 @@ namespace Tqdev\PhpCrudApi\Middleware {
8517 8516
     }
8518 8517
 }
8519 8518
 
8519
+// file: src/Tqdev/PhpCrudApi/Middleware/XmlMiddleware.php
8520
+namespace Tqdev\PhpCrudApi\Middleware {
8521
+
8522
+    use Psr\Http\Message\ResponseInterface;
8523
+    use Psr\Http\Message\ServerRequestInterface;
8524
+    use Psr\Http\Server\RequestHandlerInterface;
8525
+    use Tqdev\PhpCrudApi\Column\ReflectionService;
8526
+    use Tqdev\PhpCrudApi\Controller\Responder;
8527
+    use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
8528
+    use Tqdev\PhpCrudApi\Middleware\Router\Router;
8529
+    use Tqdev\PhpCrudApi\RequestUtils;
8530
+    use Tqdev\PhpCrudApi\ResponseFactory;
8531
+
8532
+    class XmlMiddleware extends Middleware
8533
+    {
8534
+        private $reflection;
8535
+
8536
+        public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
8537
+        {
8538
+            parent::__construct($router, $responder, $properties);
8539
+            $this->reflection = $reflection;
8540
+        }
8541
+
8542
+        private function convertFromJsonToXml(string $body): string
8543
+        {
8544
+            $objectElement = $this->getProperty('objectElement', 'object');
8545
+            $prolog = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
8546
+            $xml = new \SimpleXMLElement($prolog . '<root></root>');
8547
+            $object = json_decode($body);
8548
+            if (is_scalar($object)) {
8549
+                $xml = $xml->addChild($objectElement, $object);
8550
+            } else {
8551
+                $xml = $xml->addChild($objectElement);
8552
+                $this->convertFromObjectToXml($object, $xml, $objectElement);
8553
+            }
8554
+            return $prolog . $xml->asXML();
8555
+        }
8556
+
8557
+        private function convertFromObjectToXml($object, $xml, string $objectElement): void
8558
+        {
8559
+            if (is_array($object)) {
8560
+                $xml->addAttribute('type', 'list');
8561
+            }
8562
+            foreach ($object as $key => $value) {
8563
+                if (!is_array($value) && !is_object($value)) {
8564
+                    if (is_object($object)) {
8565
+                        $xml->addChild($key, (string) $value);
8566
+                    } else {
8567
+                        $xml->addChild($objectElement, (string) $value);
8568
+                    }
8569
+                    continue;
8570
+                }
8571
+                $node = $xml;
8572
+                if (is_object($object)) {
8573
+                    $node = $node->addChild($key);
8574
+                } elseif (is_object($value)) {
8575
+                    $node = $node->addChild($objectElement);
8576
+                }
8577
+                $this->convertFromObjectToXml($value, $node, $objectElement);
8578
+            }
8579
+        }
8580
+
8581
+        private function convertFromXmlToJson(string $body)/*: object */
8582
+        {
8583
+            $objectElement = $this->getProperty('objectElement', 'object');
8584
+            $prolog = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
8585
+            $xml = new \SimpleXMLElement($prolog . $body);
8586
+            $object = $this->convertFromXmlToObject($xml, $objectElement);
8587
+            return json_decode(json_encode($object));
8588
+        }
8589
+
8590
+        private function convertFromXmlToObject($xml): array
8591
+        {
8592
+            $result = [];
8593
+            foreach ($xml->children() as $nodeName => $nodeValue) {
8594
+                if (count($nodeValue->children()) == 0) {
8595
+                    $object = strVal($nodeValue);
8596
+                } else {
8597
+                    $object = $this->convertFromXmlToObject($nodeValue);
8598
+                }
8599
+                $attributes = $xml->attributes();
8600
+                if ($attributes['type'] == 'list') {
8601
+                    $result[] = $object;
8602
+                } else {
8603
+                    $result[$nodeName] = $object;
8604
+                }
8605
+            }
8606
+            return $result;
8607
+        }
8608
+
8609
+        public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
8610
+        {
8611
+            $operation = RequestUtils::getOperation($request);
8612
+
8613
+            parse_str($request->getUri()->getQuery(), $params);
8614
+            $isXml = isset($params['format']) && $params['format'] == 'xml';
8615
+            if ($isXml) {
8616
+                $body = $request->getBody()->getContents();
8617
+                if ($body) {
8618
+                    $json = $this->convertFromXmlToJson($body);
8619
+                    $request = $request->withParsedBody($json);
8620
+                }
8621
+            }
8622
+            $response = $next->handle($request);
8623
+            if ($isXml) {
8624
+                $body = $response->getBody()->getContents();
8625
+                if ($body) {
8626
+                    $xml = $this->convertFromJsonToXml($body);
8627
+                    $response = ResponseFactory::fromXml(ResponseFactory::OK, $xml);
8628
+                }
8629
+            }
8630
+            return $response;
8631
+        }
8632
+    }
8633
+}
8634
+
8520 8635
 // file: src/Tqdev/PhpCrudApi/Middleware/XsrfMiddleware.php
8521 8636
 namespace Tqdev\PhpCrudApi\Middleware {
8522 8637
 
@@ -10386,6 +10501,7 @@ namespace Tqdev\PhpCrudApi {
10386 10501
     use Tqdev\PhpCrudApi\Middleware\IpAddressMiddleware;
10387 10502
     use Tqdev\PhpCrudApi\Middleware\JoinLimitsMiddleware;
10388 10503
     use Tqdev\PhpCrudApi\Middleware\JwtAuthMiddleware;
10504
+    use Tqdev\PhpCrudApi\Middleware\XmlMiddleware;
10389 10505
     use Tqdev\PhpCrudApi\Middleware\MultiTenancyMiddleware;
10390 10506
     use Tqdev\PhpCrudApi\Middleware\PageLimitsMiddleware;
10391 10507
     use Tqdev\PhpCrudApi\Middleware\ReconnectMiddleware;
@@ -10467,6 +10583,9 @@ namespace Tqdev\PhpCrudApi {
10467 10583
                     case 'customization':
10468 10584
                         new CustomizationMiddleware($router, $responder, $properties, $reflection);
10469 10585
                         break;
10586
+                    case 'xml':
10587
+                        new XmlMiddleware($router, $responder, $properties, $reflection);
10588
+                        break;
10470 10589
                 }
10471 10590
             }
10472 10591
             foreach ($config->getControllers() as $controller) {
@@ -10934,6 +11053,11 @@ namespace Tqdev\PhpCrudApi {
10934 11053
         const UNPROCESSABLE_ENTITY = 422;
10935 11054
         const INTERNAL_SERVER_ERROR = 500;
10936 11055
 
11056
+        public static function fromXml(int $status, string $xml): ResponseInterface
11057
+        {
11058
+            return self::from($status, 'text/xml', $xml);
11059
+        }
11060
+
10937 11061
         public static function fromCsv(int $status, string $csv): ResponseInterface
10938 11062
         {
10939 11063
             $response = self::from($status, 'text/csv', $csv);

+ 4
- 0
src/Tqdev/PhpCrudApi/Api.php View File

@@ -25,6 +25,7 @@ use Tqdev\PhpCrudApi\Middleware\FirewallMiddleware;
25 25
 use Tqdev\PhpCrudApi\Middleware\IpAddressMiddleware;
26 26
 use Tqdev\PhpCrudApi\Middleware\JoinLimitsMiddleware;
27 27
 use Tqdev\PhpCrudApi\Middleware\JwtAuthMiddleware;
28
+use Tqdev\PhpCrudApi\Middleware\XmlMiddleware;
28 29
 use Tqdev\PhpCrudApi\Middleware\MultiTenancyMiddleware;
29 30
 use Tqdev\PhpCrudApi\Middleware\PageLimitsMiddleware;
30 31
 use Tqdev\PhpCrudApi\Middleware\ReconnectMiddleware;
@@ -106,6 +107,9 @@ class Api implements RequestHandlerInterface
106 107
                 case 'customization':
107 108
                     new CustomizationMiddleware($router, $responder, $properties, $reflection);
108 109
                     break;
110
+                case 'xml':
111
+                    new XmlMiddleware($router, $responder, $properties, $reflection);
112
+                    break;
109 113
             }
110 114
         }
111 115
         foreach ($config->getControllers() as $controller) {

+ 0
- 1
src/Tqdev/PhpCrudApi/Middleware/CorsMiddleware.php View File

@@ -5,7 +5,6 @@ namespace Tqdev\PhpCrudApi\Middleware;
5 5
 use Psr\Http\Message\ResponseInterface;
6 6
 use Psr\Http\Message\ServerRequestInterface;
7 7
 use Psr\Http\Server\RequestHandlerInterface;
8
-use Tqdev\PhpCrudApi\Controller\Responder;
9 8
 use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
10 9
 use Tqdev\PhpCrudApi\Record\ErrorCode;
11 10
 use Tqdev\PhpCrudApi\ResponseFactory;

+ 115
- 0
src/Tqdev/PhpCrudApi/Middleware/XmlMiddleware.php View File

@@ -0,0 +1,115 @@
1
+<?php
2
+
3
+namespace Tqdev\PhpCrudApi\Middleware;
4
+
5
+use Psr\Http\Message\ResponseInterface;
6
+use Psr\Http\Message\ServerRequestInterface;
7
+use Psr\Http\Server\RequestHandlerInterface;
8
+use Tqdev\PhpCrudApi\Column\ReflectionService;
9
+use Tqdev\PhpCrudApi\Controller\Responder;
10
+use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
11
+use Tqdev\PhpCrudApi\Middleware\Router\Router;
12
+use Tqdev\PhpCrudApi\RequestUtils;
13
+use Tqdev\PhpCrudApi\ResponseFactory;
14
+
15
+class XmlMiddleware extends Middleware
16
+{
17
+    private $reflection;
18
+
19
+    public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
20
+    {
21
+        parent::__construct($router, $responder, $properties);
22
+        $this->reflection = $reflection;
23
+    }
24
+
25
+    private function convertFromJsonToXml(string $body): string
26
+    {
27
+        $objectElement = $this->getProperty('objectElement', 'object');
28
+        $prolog = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
29
+        $xml = new \SimpleXMLElement($prolog . '<root></root>');
30
+        $object = json_decode($body);
31
+        if (is_scalar($object)) {
32
+            $xml = $xml->addChild($objectElement, $object);
33
+        } else {
34
+            $xml = $xml->addChild($objectElement);
35
+            $this->convertFromObjectToXml($object, $xml, $objectElement);
36
+        }
37
+        return $prolog . $xml->asXML();
38
+    }
39
+
40
+    private function convertFromObjectToXml($object, $xml, string $objectElement): void
41
+    {
42
+        if (is_array($object)) {
43
+            $xml->addAttribute('type', 'list');
44
+        }
45
+        foreach ($object as $key => $value) {
46
+            if (!is_array($value) && !is_object($value)) {
47
+                if (is_object($object)) {
48
+                    $xml->addChild($key, (string) $value);
49
+                } else {
50
+                    $xml->addChild($objectElement, (string) $value);
51
+                }
52
+                continue;
53
+            }
54
+            $node = $xml;
55
+            if (is_object($object)) {
56
+                $node = $node->addChild($key);
57
+            } elseif (is_object($value)) {
58
+                $node = $node->addChild($objectElement);
59
+            }
60
+            $this->convertFromObjectToXml($value, $node, $objectElement);
61
+        }
62
+    }
63
+
64
+    private function convertFromXmlToJson(string $body)/*: object */
65
+    {
66
+        $objectElement = $this->getProperty('objectElement', 'object');
67
+        $prolog = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
68
+        $xml = new \SimpleXMLElement($prolog . $body);
69
+        $object = $this->convertFromXmlToObject($xml, $objectElement);
70
+        return json_decode(json_encode($object));
71
+    }
72
+
73
+    private function convertFromXmlToObject($xml): array
74
+    {
75
+        $result = [];
76
+        foreach ($xml->children() as $nodeName => $nodeValue) {
77
+            if (count($nodeValue->children()) == 0) {
78
+                $object = strVal($nodeValue);
79
+            } else {
80
+                $object = $this->convertFromXmlToObject($nodeValue);
81
+            }
82
+            $attributes = $xml->attributes();
83
+            if ($attributes['type'] == 'list') {
84
+                $result[] = $object;
85
+            } else {
86
+                $result[$nodeName] = $object;
87
+            }
88
+        }
89
+        return $result;
90
+    }
91
+
92
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
93
+    {
94
+        $operation = RequestUtils::getOperation($request);
95
+
96
+        parse_str($request->getUri()->getQuery(), $params);
97
+        $isXml = isset($params['format']) && $params['format'] == 'xml';
98
+        if ($isXml) {
99
+            $body = $request->getBody()->getContents();
100
+            if ($body) {
101
+                $json = $this->convertFromXmlToJson($body);
102
+                $request = $request->withParsedBody($json);
103
+            }
104
+        }
105
+        $response = $next->handle($request);
106
+        if ($isXml) {
107
+            $body = $response->getBody()->getContents();
108
+            if ($body) {
109
+                $xml = $this->convertFromJsonToXml($body);
110
+                $response = ResponseFactory::fromXml(ResponseFactory::OK, $xml);
111
+            }
112
+        }
113
+        return $response;
114
+    }
115
+}

+ 5
- 0
src/Tqdev/PhpCrudApi/ResponseFactory.php View File

@@ -16,6 +16,11 @@ class ResponseFactory
16 16
     const UNPROCESSABLE_ENTITY = 422;
17 17
     const INTERNAL_SERVER_ERROR = 500;
18 18
 
19
+    public static function fromXml(int $status, string $xml): ResponseInterface
20
+    {
21
+        return self::from($status, 'text/xml', $xml);
22
+    }
23
+
19 24
     public static function fromCsv(int $status, string $csv): ResponseInterface
20 25
     {
21 26
         $response = self::from($status, 'text/csv', $csv);

+ 1
- 1
tests/config/base.php View File

@@ -4,7 +4,7 @@ $settings = [
4 4
     'username' => 'incorrect_username',
5 5
     'password' => 'incorrect_password',
6 6
     'controllers' => 'records,columns,cache,openapi,geojson',
7
-    'middlewares' => 'cors,reconnect,dbAuth,jwtAuth,basicAuth,authorization,sanitation,validation,ipAddress,multiTenancy,pageLimits,joinLimits,customization',
7
+    'middlewares' => 'xml,cors,reconnect,dbAuth,jwtAuth,basicAuth,authorization,sanitation,validation,ipAddress,multiTenancy,pageLimits,joinLimits,customization',
8 8
     'dbAuth.mode' => 'optional',
9 9
     'dbAuth.returnedColumns' => 'id,username,password',
10 10
     'jwtAuth.mode' => 'optional',

+ 93
- 0
tests/functional/001_records/087_read_and_write_posts_as_xml.log View File

@@ -0,0 +1,93 @@
1
+===
2
+GET /records/posts/1?format=xml
3
+===
4
+200
5
+Content-Type: text/xml
6
+Content-Length: 145
7
+
8
+<?xml version="1.0" encoding="UTF-8"?>
9
+<object><id>1</id><user_id>1</user_id><category_id>1</category_id><content>blog started</content></object>
10
+===
11
+GET /records/posts?limit=2
12
+===
13
+200
14
+Content-Type: application/json
15
+Content-Length: 134
16
+
17
+{"records":[{"id":1,"user_id":1,"category_id":1,"content":"blog started"},{"id":2,"user_id":1,"category_id":2,"content":"It works!"}]}
18
+===
19
+GET /records/posts?limit=2&format=xml
20
+===
21
+200
22
+Content-Type: text/xml
23
+Content-Length: 296
24
+
25
+<?xml version="1.0" encoding="UTF-8"?>
26
+<object><records type="list"><object><id>1</id><user_id>1</user_id><category_id>1</category_id><content>blog started</content></object><object><id>2</id><user_id>1</user_id><category_id>2</category_id><content>It works!</content></object></records></object>
27
+===
28
+GET /records/posts/1?join=users&format=xml
29
+===
30
+200
31
+Content-Type: text/xml
32
+Content-Length: 217
33
+
34
+<?xml version="1.0" encoding="UTF-8"?>
35
+<object><id>1</id><user_id><id>1</id><username>user1</username><password>pass1</password><location/></user_id><category_id>1</category_id><content>blog started</content></object>
36
+===
37
+GET /records/posts/1?join=users&join=comments,categories
38
+===
39
+200
40
+Content-Type: application/json
41
+Content-Length: 321
42
+
43
+{"id":1,"user_id":{"id":1,"username":"user1","password":"pass1","location":null},"category_id":1,"content":"blog started","comments":[{"id":1,"post_id":1,"message":"great","category_id":{"id":3,"name":"comment","icon":null}},{"id":2,"post_id":1,"message":"fantastic","category_id":{"id":3,"name":"comment","icon":null}}]}
44
+===
45
+GET /records/posts/1?join=users&join=comments,categories&format=xml
46
+===
47
+200
48
+Content-Type: text/xml
49
+Content-Length: 524
50
+
51
+<?xml version="1.0" encoding="UTF-8"?>
52
+<object><id>1</id><user_id><id>1</id><username>user1</username><password>pass1</password><location/></user_id><category_id>1</category_id><content>blog started</content><comments type="list"><object><id>1</id><post_id>1</post_id><message>great</message><category_id><id>3</id><name>comment</name><icon/></category_id></object><object><id>2</id><post_id>1</post_id><message>fantastic</message><category_id><id>3</id><name>comment</name><icon/></category_id></object></comments></object>
53
+===
54
+GET /records/posts?page=2,1
55
+===
56
+200
57
+Content-Type: application/json
58
+Content-Length: 84
59
+
60
+{"records":[{"id":2,"user_id":1,"category_id":2,"content":"It works!"}],"results":2}
61
+===
62
+GET /records/posts?page=2,1&format=xml
63
+===
64
+200
65
+Content-Type: text/xml
66
+Content-Length: 210
67
+
68
+<?xml version="1.0" encoding="UTF-8"?>
69
+<object><records type="list"><object><id>2</id><user_id>1</user_id><category_id>2</category_id><content>It works!</content></object></records><results>2</results></object>
70
+===
71
+POST /records/posts?format=xml
72
+Content-Type: application/xml
73
+
74
+<object><id>1</id><user_id>1</user_id><category_id>1</category_id><content>blog started</content></object>
75
+===
76
+200
77
+Content-Type: text/xml
78
+Content-Length: 115
79
+
80
+<?xml version="1.0" encoding="UTF-8"?>
81
+<object><code>1009</code><message>Duplicate key exception</message></object>
82
+===
83
+PUT /records/posts/1?format=xml
84
+Content-Type: application/xml
85
+
86
+<object><user_id>1</user_id><category_id>1</category_id><content>blog started</content></object>
87
+===
88
+200
89
+Content-Type: text/xml
90
+Content-Length: 57
91
+
92
+<?xml version="1.0" encoding="UTF-8"?>
93
+<object>1</object>

+ 28
- 0
tests/functional/001_records/088_read_and_write_multiple_posts_as_xml.log View File

@@ -0,0 +1,28 @@
1
+===
2
+GET /records/posts/1,2
3
+===
4
+200
5
+Content-Type: application/json
6
+Content-Length: 122
7
+
8
+[{"id":1,"user_id":1,"category_id":1,"content":"blog started"},{"id":2,"user_id":1,"category_id":2,"content":"It works!"}]
9
+===
10
+GET /records/posts/1,2?format=xml
11
+===
12
+200
13
+Content-Type: text/xml
14
+Content-Length: 277
15
+
16
+<?xml version="1.0" encoding="UTF-8"?>
17
+<object type="list"><object><id>1</id><user_id>1</user_id><category_id>1</category_id><content>blog started</content></object><object><id>2</id><user_id>1</user_id><category_id>2</category_id><content>It works!</content></object></object>
18
+===
19
+PUT /records/posts/1,2?format=xml
20
+
21
+<object type="list"><object><user_id>1</user_id></object><object><user_id>2</user_id></object></object>
22
+===
23
+200
24
+Content-Type: text/xml
25
+Content-Length: 104
26
+
27
+<?xml version="1.0" encoding="UTF-8"?>
28
+<object type="list"><object>1</object><object>1</object></object>

+ 9
- 0
tests/functional/003_columns/017_get_barcodes_table_as_xml.log View File

@@ -0,0 +1,9 @@
1
+===
2
+GET /columns/barcodes?format=xml
3
+===
4
+200
5
+Content-Type: text/xml
6
+Content-Length: 489
7
+
8
+<?xml version="1.0" encoding="UTF-8"?>
9
+<object><name>barcodes</name><type>table</type><columns type="list"><object><name>id</name><type>integer</type><pk>1</pk></object><object><name>product_id</name><type>integer</type><fk>products</fk></object><object><name>hex</name><type>varchar</type><length>255</length></object><object><name>bin</name><type>blob</type></object><object><name>ip_address</name><type>varchar</type><length>15</length><nullable>1</nullable></object></columns></object>

Loading…
Cancel
Save