CakePHP 4 – Rendering an XML view to a variable

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

You May Also Like…

PHPSECLIB3 Client Algorithms

Recently our EDI supplier notified that they were changing their SFTP encryption and data integrity algorithms I use...