PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Sunday, January 16, 2022

[FIXED] Get form helper to automatically assing default values with cakephp 3.0 beta2

 January 16, 2022     cakephp, cakephp-3.0, xml     No comments   

Issue

I am developing a web application that should handle XML data, and I'm facing some problems when I try to edit data that I have previously saved.

This is What I'm doing from the beginning: The user has to fill a form with several inputs, I grab that data in my controller, convert it into an XML string with Xml::fromArray and store it in a single database field. This works perfectly and the generated XML string is exactly what I need. This is (part of) the form.

echo $this->Form->input('data.FT.Header.Transmission.Number');
echo $this->Form->input('data.FT.Header.Transmission.Format');
echo $this->Form->input('data.FT.Header.Transmission.Destination');

Now I am trying to make add an edit function for that data. From my controller I grab that data, convert it back to an object with Xml::build and... this is where the problem starts.

If I do this

debug($invoice->data->FT->Header->Transmission);

everything looks fine as it tells me that I have an object containing this:

object(SimpleXMLElement) {
    Number => 'Npslu'
    Format => 'Rgytz'
    Destination => 'Uheac'
}

But, if I try to see what $invoice->data->FT->Header->Transmission->Number contains, instead of a string I get an empty result. Which is why (I think) all the form inputs are empty.

I had a look to the official documentation, tried to convert it to arrays (which should be the old way cakephp handled data) and everything that came to my mind... still no results.

What am I doing wrong?


UPDATE: I managed to create a working object this (dirty) way:

$array = Xml::toArray( Xml::build( $xmlString ) );
$object = json_decode(json_encode($array), FALSE);

Now, if I debug $invoice->data->FT->Header->Transmission I have the correct value, which is a huge step forward in my situation :)

The problem is that the Form helper still don't recognize that value. If I have this in my edit template

echo $this->Form->input('data.FT.Header.Transmission.Number');

then the field is empty. If I specify its value this way

echo $this->Form->input('data.FT.Header.Transmission.Number', array('value' => $invoice->data->FT->Header->Transmission->Number);

it works correctly as it should. I know I could specify every value but I'd like to have it automatically, as it does with other fields (non XML related) that I have in that page, like $invoice->sent which is correctly recognized by $this->Form->input('sent')...


UPDATE 2: this is the XML generated code:

<?xml version="1.0" encoding="UTF-8"?>
<FT>
   <Header>
      <Transmission>
         <Number>1234</Number>
         <Format>qwer</Format>
         <Destination>asdf</Destination>
      </Transmission>
   </Header>
</FT>

And these are the functions I'm using to convert the input field's values to an XML and back:

// Values to XML, called from the add function this way:
// $this->request->data['data'] = $this->_arrayToXml($this->request->data['data']);
protected function _arrayToXml($array) {
    $xmlObject = Xml::fromArray($array);
    $xmlString = $xmlObject->asXML();
    return $xmlString;
}
// XML to Values, called from the edit function this way:
// $invoice->data = $this->_xmlToObject($invoice->data);
protected function _xmlToObject($xmlString) {
    $array = Xml::toArray( Xml::build( $xmlString ) );
    $object = json_decode(json_encode($array), FALSE);
    return $object;
}

p.s. please forgive me if you see some minor incongruences, I'm trying to strip all non related code to avoid posting tons of code...


Solution

Empty SimpleXMLElement objects

The object returned actually isn't empty, Cakes debug() function just can't handle objects like SimpleXMLElement yet, where the values aren't explicitly stored in properties. Use var_dump() or cast the value and you'll see that it's there:

var_dump($invoice->data->FT->Header->Transmission);
debug((string)$invoice->data->FT->Header->Transmission);

Mystery solved.

Values not shown in form inputs

Now why aren't the values appearing in the form inputs? Well, data in forms is accessed via context providers, and since you are passing an entity, the form uses the entity context provider.

The entity provider tries to fetch the data from the entity in case it's not available in the request object, which is the case for an edit form that wasn't yet submitted. The problem here is that the provider cannot handle your data structure, it expects nested entities, not arrays or standard/SimpleXML objects.

The most simple way to achieve what you are trying to do would be to always set the request data, so that the entity context provider doesn't try to fetch the data from the entity, but always grabs it from the request data (see EntityContext::val()). Something like:

Controller

public function edit($id = null) {
    // ...
    if ($this->request->is(['post', 'put'])) {
        // convert array input data to XML string
        $this->request->data['data'] =
            Xml::fromArray($this->request->data['data'])->asXML();

        $invoice = $this->Invoices->patchEntity($invoice, $this->request->data);
        if ($this->Invoices->save($invoice)) {
            // ...
        }
    }

    // convert XML string to array
    $this->request->data['data'] = XML::toArray(Xml::build($invoice->data));

    // ...
    $this->set(compact('invoice'));
}

That way the entity is not being touched when obtaining data for the inputs, and everything should work fine.

A possibly cleaner approach

A possibly cleaner, but somewhat more complex approach might be to use a custom data type that converts the data, and a custom context provider that can handle dot notated path access to XML objects. That way there is no need for any special conversion etc in the controller.

However, this isn't really part of the question/problem, so I'm just broaching the subject here by providing an example (not tested thoroughly) with some hints.

XML Data Type

This converts the string retrieved from the database into an XML object which the entity is going to expose, and it marshalls the array request input data into an XML string

namespace App\Database\Type;

use Cake\Database\Driver;
use Cake\Database\Type;
use Cake\Utility\Xml;

class XmlType extends Type {

    public function toPHP($value, Driver $driver)
    {
        if($value === null)
        {
            return null;
        }
        return Xml::build($value);
    }

    public function marshal($value) {
        return Xml::fromArray($value)->asXML();
    }

}

XML Entity Context

This extracts the data from the XML object in case necessary. The outer parts are copied from the original Entity::val() method.

namespace App\View\Form;

use Cake\ORM\Entity;
use Cake\View\Form\EntityContext;

class XmlEntityContext extends EntityContext
{
    public function val($field) {
        $val = $this->_request->data($field);
        if ($val !== null) {
            return $val;
        }
        if (empty($this->_context['entity'])) {
            return null;
        }
        $parts = explode('.', $field);
        $entity = $this->_getEntity($parts);

        // begin: special XML element treatment
        if($entity instanceof \SimpleXMLElement)
        {
            array_shift($parts);
            return current($entity->xpath('/' . implode('/', $parts)));
        }
        // end: special XML element treatment

        if (end($parts) === '_ids' && !empty($entity)) {
            return $this->_extractMultiple($entity, $parts);
        }

        if ($entity instanceof Entity) {
            return $entity->get(array_pop($parts));
        }
        return null;
    }
}

See

For more information, check

  • http://book.cakephp.org/3.0/en/orm/database-basics.html#data-types
  • http://book.cakephp.org/3.0/en/orm/database-basics.html#adding-custom-types
  • http://book.cakephp.org/3.0/en/views/helpers/form.html#creating-context-classes


Answered By - ndm
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing