Written by James McDonald

May 17, 2022

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'
// 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.

// 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
<?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
<?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>

 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

<?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;
    }
}

<?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.

You May Also Like…

MacOS USB Creator

Just toasted my Windows 10 Pro install with a Windows 11 upgrade. Think it will be unrecoverable (because of Bitlocker...