CakePHP 4 – Rendering an XML view to a variable

by | May 17, 2022 | IT Tips | 0 comments

Rendering XML to a variable using CakePHP's XmlView class
Learnings
  • Instantiating and using ViewBuilder
  • You don't need ->set() if you pass the data to ->build()
  • You can move XML creation into its own class to clean your controller action up and use it elsewhere in your code
  • Identify what you want converted to XML using ->setOption('serialize'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// controller action
public function xml()
    {
        // this can be a $this->Items->find('all') call
       // setting attributes
        $items = [
            ['hi' => 'yes''@attribute' => "attribute",],
            ['hi' => 'no']
        ];
 
        // this example shows instantiating ViewBuilder
        // you could use this to generate XML output elsewhere in your code
        // not just inside a controller action
        $vb = new ViewBuilder();
 
        $vb->setClassName('Xml')->setOption('serialize', 'items');
         $vb->setTemplate(false);
        $view = $vb->build();
 
        $view->set(compact('items'));
 
        $xml = $view->render();
 
        $data = ['data' => $xml, 'filename' => uniqid('hijames') . '.xml'];
 
        $options = ['config' => 'default'];
 
        QueueManager::push(SFTPSendJob::class, $data, $options);
        
        // stops the action looking for a view template file
        $this->autoRender = false;
        // still output the XML to the browser but without a view template
        return $this->response->withType('xml')->withStringBody($xml);
    }

You don't have to create ViewBuilder from scratch you can just use $this->viewBuilder just capture the XmlView to a variable and then set data to it.

1
2
3
4
5
6
7
8
9
// in controller just use $this->viewBuilder()
$this->viewBuilder()->setClassName('Xml')
            ->setOption('serialize', 'items');
 
$view = $this->viewBuilder()->build();
 
$view->set(compact('items'));
 
$xml = $view->render();
Contents of $xml
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<response>
  <items attribute="attribute">
    <hi>yes</hi>
  </items>
  <items>
    <hi>no</hi>
  </items>
</response>
Another Example with Custom RootNode and attributes on RootNode
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<myItems myattribute="This is my attribute" filename="hijames62647c4e016d4.xml">
  <items>
    <hi>yes</hi>
    <sub>
      <hi>there</hi>
    </sub>
  </items>
  <items attribute="attributeOnTheChildHereButAppearsOneLevelUp">
    <hi>no</hi>
  </items>
</myItems>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public function xml()
   {
       // Custom finder
       // $items = $this->Items->find('validItems', ['productTypeId' => 1]);
 
       // if passing to a beanstalk queue you have to limit the amount of data or reconfigure
       // beanstalk to accept larger JSON payloads
       // $items = $this->Items->find('all')->where(['active' => 1])->limit(1);
 
       $filename = uniqid('hijames') . '.xml';
 
       // build your own XML
       $items = [
           ['hi' => 'yes', 'sub' => ['hi' => 'there']],
           ['hi' => 'no', '@attribute' => "attributeOnTheChildHereButAppearsOneLevelUp"]
       ];
 
       $this->viewBuilder()
           ->setClassName('Xml')
           ->setOption('rootNode', 'myItems') // custom root node <hiJames></hiJames>
           ->setOption('serialize', ['@myattribute', 'items', '@filename']);
 
        // Can i just build without set if I pass vars to build?
       // Yes!!!
       $view = $this->viewBuilder()->build([
           'items' => $items,
           '@myattribute' => "This is my attribute",
           '@filename' => $filename
       ]);
 
       $data = ['data' => $xml, 'filename' => $filename];
 
       $options = ['config' => 'default'];
        
       // this is the CakePHP/Queue plugin Pushing to beanstalk or redis queue
       QueueManager::push(SFTPSendJob::class, $data, $options);
 
       // don't try to pick up template in Items/xml
       $this->autoRender = false;
 
       // also render the xml to browser
       return $this->response->withType('xml')->withStringBody($xml);
   }

Moving XML Creation Out of Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
 
namespace App\Lib\Utility;
 
use Cake\View\ViewBuilder;
 
class XMLCreator
{
    public function create($data, $options = null)
    {
        // array_merge
        // the same key appearing later will
        // overwrite
        $options = array_merge([
            'rootNode' => 'response'
        ], $options);
 
        // + syntax if it already exists
        // + will NOT overwrite but will add if it
        // doesn't exist
        $options = $options + [
            'rootNode' => 'response'
        ];
 
        $vb = new ViewBuilder();
 
        $vb->setClassName('Xml')
            ->setOption('rootNode', $options['rootNode'])
            ->setOption('serialize', $options['serialize']);
 
        $view = $vb->build($data);
 
        $xml = $view->render();
 
        return $xml;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
class ItemsController
{
    public function xml()
    {
        // $items = $this->Items->find('validItems', ['productTypeId' => 1]);
 
        // $items = $this->Items->find('all')->where(['active' => 1]);
 
        $filename = uniqid('hijames') . '.xml';
 
        $items = [
            ['hi' => 'yes', 'sub' => ['hi' => 'there']],
            ['hi' => 'no', '@attribute' => "attributeOnTheChildHereButAppearsOneLevelUp"]
        ];
 
        $data = [
            'items' => $items,
            '@myattribute' => "This is my attribute",
            '@filename' => $filename
        ];
 
        $serialize = array_keys($data);
 
        $xml = (new XMLCreator())->create($data, [
            'rootNode' => 'rupert',
            'serialize' => $serialize
        ]);
 
        $this->autoRender = false;
 
        return $this->response->withType('xml')->withStringBody($xml);
    }
}

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.