I'm trying to serialize an XML document containing entities to insert into Doctrine MySQL database.
I got, for example, these two attributes in my entity :
$companyId
$companyName
The problem is that instead of something like this into my XML doc :
<company>
<id>8888</id>
<name>MyCompany</name>
</company>
I got something like this :
<company id="8888" name="MyCompany"/>
The XML is generated by an independant company I work with ; so I can't change it.
So the Symfony2 serializer is creating an empty $company attribute :(
Is there a simple way to costumize the seralizing process like I want ? Or do I have to implement a complete independant method ?
Thanks a lot.
I'd create a simple Denormalizer because attributes are already parsed by default XmlEncoder. It adds a special character # in at the beggining of the key.
Without tweaking alot you could add a context parameter like use_attributes which your custom denormalizer can understand. Here's an example
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
class AttributesDenormalizer implements DenormalizerInterface
{
public function __construct(DenormalizerInterface $delegate)
{
$this->delegate = $delegate;
}
public function denormalize($data, $class, $format = null, array $context = array())
{
if (!is_array($data) || !isset($context['use_attributes']) || true !== $context['use_attributes']) {
return $this->delegate->denormalize($data, $class, $format, $context);
}
$attributes = array();
foreach ($data as $key => $value) {
if (0 === strpos($key, '#')) {
$attributes[substr($key, 1)] = $value;
}
}
if (empty($attributes)) {
$attributes = $data;
}
return $this->delegate->denormalize($attributes, $class, $format, $context);
}
public function supportsDenormalization($data, $type, $format = null)
{
return $this->delegate->supportsDenormalization($data, $type, $format);
}
}
And here is an example of usage
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
$xml = '<company id="8888" name="MyCompany"/>';
$encoders = array(new XmlEncoder());
$normalizers = array(new AttributesDenormalizer(new GetSetMethodNormalizer));
$serializer = new Serializer($normalizers, $encoders);
$serializer->deserialize($xml, 'Company', 'xml', array('use_attributes' => true));
Which results in
class Company#13 (2) {
protected $id =>
string(4) "8888"
protected $name =>
string(9) "MyCompany"
}
It's now much easier to serialize XML attributes by using the #SerializeName annotation with '#'.
In your Company entity, when defining $name, add
/**
* #ORM\Column(type="string", length=255)
* #SerializedName('#name')
*/
private $name;
Now when you serialize to XML, it will come out as a property, as expected.
I know the OP was actually asking about deserialization, but hope this helps someone who is searching.
Okay so finally I tried to use the JMSSerializerBundle but my case is too complicated. I got many entities with several ManyToOne relations ; and I got bot standard and attributes values in my XML.
So I'll use your idea : create my complete whole Denormalizer. It will use the decoded XML and read it line by line, doing what it has to do (creating entities with Doctrine).
It's gonna be a huge process but the most simple one.
Thank you.
[EDIT] I finally found a pretty good solution.
I registered the links between XML and my entity setters into a yaml table
company:
#id: setCompanyId
#name : setCompanyName
address:
#city: setAddressCity
#street: setAddressStreet
...
Thanks to that, I can easily read my whole XML and, for each node/attribute value, find the setter name into the table and then do :
$company = new Company;
$company->setterNameFromTable($value);
Quite old, but I got to a much simpler solution using Serializator + xpath in SerializedName annotation, so this could be useful for someone.
Having for example this entry XML:
<root>
<company id="123456"/>
</root>
Whe deserializing into an object you could use this annotation to populate the company id into "id" property:
/**
* #Serializer\SerializedName(name="company/#id")
*/
public ?int $id = null;
PS: tested on Symfony 5.4
Related
What I'm using:
I'm using the JMSSerializerBundle to deserialize my JSON object from a POST request.
Description of the problem:
One of the vaules in my JSON is an Id. I'd like to replace this Id with the correct object before the deserialization occurs.
Unfortunately, JMSSerializerBundle does not have a #preDeserializer annotation.
The problem I'm facing (and that I would have faced if there was an #preDeserializer annotation anyway) is that I would like to create a generic function for all my entities.
Question:
How do I replace my Id with the corresponding object in the most generic way possible ?
You also do your own hydratation as I did (with Doctrine):
Solution
The IHydratingEntity is an interface which all my entities implement.
The hydrate function is used in my BaseService generically. Parameters are the entity and the json object.
At each iteration, the function will test if the method exists then it will call the reflection function to check if the parameter's method (setter) also implements IHydratingEntity.
If it's the case, I use the id to get the entity from the database with Doctrine ORM.
I think it's possible to optimize this process, so please be sure to share your thoughts !
protected function hydrate(IHydratingEntity $entity, array $infos)
{
#->Verification
if (!$entity) exit;
#->Processing
foreach ($infos as $clef => $donnee)
{
$methode = 'set'.ucfirst($clef);
if (method_exists($entity, $methode))
{
$donnee = $this->reflection($entity, $methode, $donnee);
$entity->$methode($donnee);
}
}
}
public function reflection(IHydratingEntity $entity, $method, $donnee)
{
#->Variable declaration
$reflectionClass = new \ReflectionClass($entity);
#->Verification
$idData = intval($donnee);
#->Processing
foreach($reflectionClass->getMethod($method)->getParameters() as $param)
{
if ($param->getClass() != null)
{
if ($param->getClass()->implementsInterface(IEntity::class))
#->Return
return $this->getDoctrine()->getRepository($param->getClass()->name)->find($idData);
}
}
#->Return
return $donnee;
}
I have been using Behat for a year or so at a level fine for the automation of most websites but I now need to start using it more for user generated content, I am relatively new to PHP and at the moment I am struggling how to use a String entered in an Example table in an x-path array:
Feature: Campaign
Scenario Outline: Pass campaign string to xpath array
Then I add a new campaign name of "<campaign>"
Examples:
|campaign |
|Automation|
The context file looks like this
/**
* #Then /^I add a new campaign name of "([^"]*)"$/
*/
public function iAddANewCampaignNameOf($campaign)
{
/**
* #var CreateCampaign $createCampaign
*/
$createCampaign= $this->getPage('CreateCampaign');
$createCampaign->campaignName($campaign);
}
Then I use the Page Object extension for the class Campaign.php
class CreateCampaign extends AutomationPage
{
protected $path = 'someURL';
public $campaign;
protected $elements = array(
'campaignHeader' => array('xpath' => "//*[#id='site-navigation-campaigns']"),
);
public function campaignName ($campaign)
{
$this->campaign = $campaign;
$this->getSession()->wait(5000);
$this->getElement('campaignName')->setValue($campaign);
}
So far so good, the tester can enter a campaign name of "Automation" - it gets passed through the context file and the campaign name is set in the browser.
What I am lacking is to be able to retain this $campaign name string and use it in another page so I can reference it in another array i.e. for selecting an existing campaign as follows:
SecondPageObjectPage.php
class ReferenceCampaign extends AutomationPage
{
protected $path = 'someURL';
protected $elements = array(
'referenceCampaign' => array('xpath' => "//*[contains(#id,'***HERE I NEED TO GET THE
$campaign value"),
);
public function editExistingCampaign ($campaign)
{
$this->getElement('referenceCampaign')->click();
}
}
I have tried my best to simplify things and I can explain further if any of this isnt clear - hopefully its just a simple PHP question and not really Behat specific
Thanks Ian
Your example is a much better way of doing things, I have only recently started using partial contains and it expands the flexibility of finding stubborn xpaths especially if you combine more than one, like the working example below:
public function editExistingCampaign ($campaign)
{
$this->getSession()->wait(5000);
$element = $this->find('xpath', '//*[contains(#id,"'.$campaign.'")]
[contains(#id,"actionbuttons")]');
if (isset($element)) {
$element->click();
} else {
throw new Exception('Element not found');
}
}
The only slight change was to add a ] at the end of the x-path
I'm sure it's a simple question, but I think that I am missing a point. If all you want is to get hold of the value that was used on the page then you need to review your code structure. First, you cannot pass method argument to the property definition in another class, but you can find the element inside editExistingCampaign.
class ReferenceCampaign extends AutomationPage
{
protected $path = 'someURL';
public function editExistingCampaign ($campaign)
{
$element = $this->find('xpath', '//*[contains(#id, "' . $campaign . '")]');
if (isset($element)) {
$element->click();
} else {
throw new Exception('Element not found');
}
}
}
I'm assuming you are using Symfony Page Object extension, which you should mention. I'm not sure if I've got the syntax right, but the idea is to find your element inside the method.
As I've finally found a binary of memcache for PHP 5.4.4 on Windows, I'm speeding up the application I'm currently developing.
I've succeeded setting memcache as Doctrine ORM Mapping Cache driver, but I need to fix another leakage: Forms built using annotations.
I'm creating forms according to the Annotations section of the docs. Unfortunately, this takes a lot of time, especially when creating multiple forms for a single page.
Is it possible to add caching to this process? I've browsed through the code but it seems like the Zend\Form\Annotation\AnnotationBuilder always creates the form by reflecting the code and parsing the annotations. Thanks in advance.
You might wanna try something like this:
class ZendFormCachedController extends Zend_Controller_Action
{
protected $_formId = 'form';
public function indexAction()
{
$frontend = array(
'lifetime' => 7200,
'automatic_serialization' => true);
$backend = array('cache_dir' => '/tmp/');
$cache = Zend_Cache::factory('Core', 'File', $frontend, $backend);
if ($this->getRequest()->isPost()) {
$form = $this->getForm(new Zend_Form);
} else if (! $form = $cache->load($this->_formId)) {
$form = $this->getForm(new Zend_Form);
$cache->save($form->__toString(), $this->_formId);
}
$this->getHelper('layout')->setLayout('zend-form');
$this->view->form = $form;
}
Found here.
Louis's answer didn't work for me so what I did was simply extend AnnotationBuilder's constructor to take a cache object and then modified getFormSpecification to use that cache to cache the result. My function is below..
Very quick work around...sure it could be improved. In my case, I was limited to some old hardware and this took the load time on a page from 10+ seconds to about 1 second
/**
* Creates and returns a form specification for use with a factory
*
* Parses the object provided, and processes annotations for the class and
* all properties. Information from annotations is then used to create
* specifications for a form, its elements, and its input filter.
*
* MODIFIED: Now uses local cache to store parsed annotations
*
* #param string|object $entity Either an instance or a valid class name for an entity
* #throws Exception\InvalidArgumentException if $entity is not an object or class name
* #return ArrayObject
*/
public function getFormSpecification($entity)
{
if (!is_object($entity)) {
if ((is_string($entity) && (!class_exists($entity))) // non-existent class
|| (!is_string($entity)) // not an object or string
) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an object or valid class name; received "%s"',
__METHOD__,
var_export($entity, 1)
));
}
}
$formSpec = NULL;
if ($this->cache) {
//generate cache key from entity name
$cacheKey = (is_string($entity) ? $entity : get_class($entity)) . '_form_cache';
//get the cached form annotations, try cache first
$formSpec = $this->cache->getItem($cacheKey);
}
if (empty($formSpec)) {
$this->entity = $entity;
$annotationManager = $this->getAnnotationManager();
$formSpec = new ArrayObject();
$filterSpec = new ArrayObject();
$reflection = new ClassReflection($entity);
$annotations = $reflection->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureForm($annotations, $reflection, $formSpec, $filterSpec);
}
foreach ($reflection->getProperties() as $property) {
$annotations = $property->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureElement($annotations, $property, $formSpec, $filterSpec);
}
}
if (!isset($formSpec['input_filter'])) {
$formSpec['input_filter'] = $filterSpec;
}
//save annotations to cache
if ($this->cache) {
$this->cache->addItem($cacheKey, $formSpec);
}
}
return $formSpec;
}
I'm pretty new to PHP, DOM, and the PHP DOM implementation. What I'm trying to do is save the root element of the DOMDocument in a $_SESSION variable so I can access it and modify it on subsequent page loads.
But I get an error in PHP when using $_SESSION to save state of DOMElement:
Warning: DOMNode::appendChild() [domnode.appendchild]: Couldn't fetch DOMElement
I have read that a PHP DOMDocument object cannot be saved to $_SESSION natively. However it can be saved by saving the serialization of the DOMDocument (e.g. $_SESSION['dom'] = $dom->saveXML()).
I don't know if the same holds true for saving a DOMElement to a $_SESSION variable as well, but that's what I was trying. My reason for wanting to do this is to use an extended class of DOMElement with one additional property. I was hoping that by saving the root DOMElement in $_SESSION that I could later retrieve the element and modify this additional property and perform a test like, if (additionalProperty === false) { do something; }. I've also read that by saving a DOMDocument, and later retrieving it, all elements are returned as objects from native DOM classes. That is to say, even if I used an extended class to create elements, the property that I subsequently need will not be accessible, because the variable holding reference to the extended-class object has gone out of scope--which is why I'm trying this other thing. I tried using the extended class (not included below) first, but got errors...so I reverted to using a DOMElement object to see if that was the problem, but I'm still getting the same errors. Here's the code:
<?php
session_start();
$rootTag = 'root';
$doc = new DOMDocument;
if (!isset($_SESSION[$rootTag])) {
$_SESSION[$rootTag] = new DOMElement($rootTag);
}
$root = $doc->appendChild($_SESSION[$rootTag]);
//$root = $doc->appendChild($doc->importNode($_SESSION[$rootTag], true));
$child = new DOMElement('child_element');
$n = $root->appendChild($child);
$ct = 0;
foreach ($root->childNodes as $ch) echo '<br/>'.$ch->tagName.' '.++$ct;
$_SESSION[$rootTag] = $doc->documentElement;
?>
This code gives the following errors (depending on whether I use appendChild directly or the commented line of code using importNode):
Warning: DOMNode::appendChild() [domnode.appendchild]: Couldn't fetch DOMElement in C:\Program Files\wamp_server_2.2\www\test2.php on line 11
Warning: DOMDocument::importNode() [domdocument.importnode]: Couldn't fetch DOMElement in C:\Program Files\wamp_server_2.2\www\test2.php on line 12
I have several questions. First, what is causing this error and how do I fix it? Also, if what I'm trying to do isn't possible, then how can I accomplish my general objective of saving the 'state' of a DOM tree while using a custom property for each element? Note that the additional property is only used in the program and is not an attribute to be saved in the XML file. Also, I can't just save the DOM back to file each time, because the DOMDocument, after a modification, may not be valid according to a schema I'm using until later when additional modificaitons/additions have been performed to the DOMDocument. That's why I need to save a temporarily invalid DOMDocument. Thanks for any advice!
EDITED:
After trying hakre's solution, the code worked. Then I moved on to trying to use an extended class of DOMElement, and, as I suspected, it did not work. Here's the new code:
<?php
session_start();
//$_SESSION = array();
$rootTag = 'root';
$doc = new DOMDocument;
if (!isset($_SESSION[$rootTag])) {
$root = new FreezableDOMElement($rootTag);
$doc->appendChild($root);
} else {
$doc->loadXML($_SESSION[$rootTag]);
$root = $doc->documentElement;
}
$child = new FreezableDOMElement('child_element');
$n = $root->appendChild($child);
$ct = 0;
foreach ($root->childNodes as $ch) {
$frozen = $ch->frozen ? 'is frozen' : 'is not frozen';
echo '<br/>'.$ch->tagName.' '.++$ct.': '.$frozen;
//echo '<br/>'.$ch->tagName.' '.++$ct;
}
$_SESSION[$rootTag] = $doc->saveXML();
/**********************************************************************************
* FreezableDOMElement class
*********************************************************************************/
class FreezableDOMElement extends DOMElement {
public $frozen; // boolean value
public function __construct($name) {
parent::__construct($name);
$this->frozen = false;
}
}
?>
It gives me the error Undefined property: DOMElement::$frozen. Like I mentioned in my original post, after saveXML and loadXML, an element originally instantiated with FreezableDOMElement is returning type DOMElement which is why the frozen property is not recognized. Is there any way around this?
You can not store a DOMElement object inside $_SESSION. It will work at first, but with the next request, it will be unset because it can not be serialized.
That's the same like for DOMDocument as you write about in your question.
Store it as XML instead or encapsulate the serialization mechanism.
You are basically facing three problems here:
Serialize the DOMDocument (you do this to)
Serialize the FreezableDOMElement (you do this to)
Keep the private member FreezableDOMElement::$frozen with the document.
As written, serialization is not available out of the box. Additionally, DOMDocument does not persist your FreezableDOMElement even w/o serialization. The following example demonstrates that the instance is not automatically kept, the default value FALSE is returned (Demo):
class FreezableDOMElement extends DOMElement
{
private $frozen = FALSE;
public function getFrozen()
{
return $this->frozen;
}
public function setFrozen($frozen)
{
$this->frozen = (bool)$frozen;
}
}
class FreezableDOMDocument extends DOMDocument
{
public function __construct()
{
parent::__construct();
$this->registerNodeClass('DOMElement', 'FreezableDOMElement');
}
}
$doc = new FreezableDOMDocument();
$doc->loadXML('<root><child></child></root>');
# own objects do not persist
$doc->documentElement->setFrozen(TRUE);
printf("Element is frozen (should): %d\n", $doc->documentElement->getFrozen()); # it is not (0)
As PHP does not so far support setUserData (DOM Level 3), one way could be to store the additional information inside a namespaced attribute with the element. This can also be serialized by creating the XML string when serializing the object and loading it when unserializing (see Serializable). This then solves all three problems (Demo):
class FreezableDOMElement extends DOMElement
{
public function getFrozen()
{
return $this->getFrozenAttribute()->nodeValue === 'YES';
}
public function setFrozen($frozen)
{
$this->getFrozenAttribute()->nodeValue = $frozen ? 'YES' : 'NO';
}
private function getFrozenAttribute()
{
return $this->getSerializedAttribute('frozen');
}
protected function getSerializedAttribute($localName)
{
$namespaceURI = FreezableDOMDocument::NS_URI;
$prefix = FreezableDOMDocument::NS_PREFIX;
if ($this->hasAttributeNS($namespaceURI, $localName)) {
$attrib = $this->getAttributeNodeNS($namespaceURI, $localName);
} else {
$this->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . $prefix, $namespaceURI);
$attrib = $this->ownerDocument->createAttributeNS($namespaceURI, $prefix . ':' . $localName);
$attrib = $this->appendChild($attrib);
}
return $attrib;
}
}
class FreezableDOMDocument extends DOMDocument implements Serializable
{
const NS_URI = '/frozen.org/freeze/2';
const NS_PREFIX = 'freeze';
public function __construct()
{
parent::__construct();
$this->registerNodeClasses();
}
private function registerNodeClasses()
{
$this->registerNodeClass('DOMElement', 'FreezableDOMElement');
}
/**
* #return DOMNodeList
*/
private function getNodes()
{
$xp = new DOMXPath($this);
return $xp->query('//*');
}
public function serialize()
{
return parent::saveXML();
}
public function unserialize($serialized)
{
parent::__construct();
$this->registerNodeClasses();
$this->loadXML($serialized);
}
public function saveBareXML()
{
$doc = new DOMDocument();
$doc->loadXML(parent::saveXML());
$xp = new DOMXPath($doc);
foreach ($xp->query('//#*[namespace-uri()=\'' . self::NS_URI . '\']') as $attr) {
/* #var $attr DOMAttr */
$attr->parentNode->removeAttributeNode($attr);
}
$doc->documentElement->removeAttributeNS(self::NS_URI, self::NS_PREFIX);
return $doc->saveXML();
}
public function saveXMLDirect()
{
return parent::saveXML();
}
}
$doc = new FreezableDOMDocument();
$doc->loadXML('<root><child></child></root>');
$doc->documentElement->setFrozen(TRUE);
$child = $doc->getElementsByTagName('child')->item(0);
$child->setFrozen(TRUE);
echo "Plain XML:\n", $doc->saveXML(), "\n";
echo "Bare XML:\n", $doc->saveBareXML(), "\n";
$serialized = serialize($doc);
echo "Serialized:\n", $serialized, "\n";
$newDoc = unserialize($serialized);
printf("Document Element is frozen (should be): %s\n", $newDoc->documentElement->getFrozen() ? 'YES' : 'NO');
printf("Child Element is frozen (should be): %s\n", $newDoc->getElementsByTagName('child')->item(0)->getFrozen() ? 'YES' : 'NO');
It's not really feature complete but a working demo. It's possible to obtain the full XML without the additional "freeze" data.
Does anyone have any clue as to how I can add an attribute to a SoapVar object? It seems like it would be simple, but I can't get it to take/work.
I've looked at the PHP docs and at the following stackoverflow question:
Documentation on SoapVar,
stackoverflow question:
SoapVar/Param and nested, repeated elements in SOAP
I'm trying to add an attribute like this array example, but using complex SoapVar objects instead.
<?php
$amount['_'] = 25;
$amount['currencyId'] = 'GBP';
$encodded = new SoapVar($amount, SOAP_ENC_OBJECT);
?>
and end result wound be
<amount currencyId="GBP">25</amount>
Thanks.
Getting attributes into SOAP elements is a bit of a hassle. The way they implemented it is a bit confusing.
First thing to do is add the attributes to the wsdl file that SoapServer uses to correctly read and respond to the SOAP requests.
<xs:complexType name="encryptionContext">
<xs:simpleContent>
<xs:extension base="xs:string">
**<xs:attribute name="type" type="tns:encryptionType" />**
</xs:extension>
</xs:simpleContent>
</xs:complexType>
We will have to tell SoapServer to use a php helper class by passing it in the options as classmap:
$soap_server = new \SoapServer($wsdl_file, array(
'cache_wsdl' => 1,
'trace' => true,
'classmap' => array('mediaCollection' => 'SoapMediaHelper')
));
What we are mapping here is the SOAP element name mediaCollection to one of our classes, SoapMediaHelper. Instead of returning arrays, we can now return a class, in this case, it's named SoapMediaHelper. The class can have soap-element=>value pairs as well as soap-attribute=>value pairs.
Assuming we already have made a class that handles mediaCollections, this tells SoapServer to map a class called SoapMediaHelper to it. The class is really simple:
class SoapMediaHelper
{
public function __construct(Array $properties = array())
{
foreach ($properties as $key => $value) {
$this->{$key} = $value;
}
}
}
The properties of this class have to be populated. These properties should be both the tagname=>value pairs as well as the attribute name and value pair(s) for the attributes we want to add to our mediaCollection. SoapServer will figure out which is which according to our wsdl file.
We will still have to populate this object, which we can do with yet another class.
class SoapVarHelper
{
public function get($the_playlist, $playlist_id, $owned_by_user){
/* The SoapMediaHelper class is mapped to the mediaCollection wsdl.
* This is only needed to be able to set attributes on the XML nodes while using php's SoapServer
* */
$media_helper = new SoapMediaHelper($the_playlist);
/* For this type, the following xml attributes have to be set. (Not in the wsdl example above.) */
if($playlist_id === 'playlists'){
$media_helper->readOnly = false;
$media_helper->userContent = true;
$media_helper->renameable = false;
$media_helper->canDeleteItems = true;
}
if($owned_by_user){
$media_helper->readOnly = false;
$media_helper->userContent = false;
$media_helper->renameable = true;
$media_helper->canDeleteItems = true;
$media_helper->canReorderItems = true;
}
return new \SoapVar($media_helper, SOAP_ENC_OBJECT);
}
}
This class should be called with the normal tagname=>value pairs. It then adds the attributes we want. In this case conditionally. We feed our SoapMediaHelper object to SoapVar. (We told SoapServer earlier that this is fine.)
Now the last thing we need to do is, in our mediaCollection class, to use the helper SoapVarHelper to return a SoapMediaHelper (the one we told SoapServer about earlier).
In our mediaCollection we have a function get_metadata_for_root:
public function get_metadata_for_root($user_id, $index, $count){
$titles = array(
'slides' => 'Featured',
);
$media_metadata = array();
foreach($titles as $key => $title){
$playlist = array(
'id' => $key,
'title' => $title,
'img' => $this->_utils->get_url() . '/public/sonos/images/browse-icons/icon-default-legacy.png'
);
**$media_metadata[] = $this->_soap_var_helper->get($playlist, $key, false);**
}
$res = array(
'count' => count($media_metadata),
'index' => 0,
'total' => count($media_metadata),
'mediaCollection' => $media_metadata
);
}
For every mediaCollection result we pass it through the soap_var_helper to make sure not only the element=>value pairs are added, but also the attributes that we want on it.
TO SUMMARIZE:
Make sure you feed the SoapServer with a wsdl file, so it know the elements and the attributes.
In the SoapServer options add classmap in order to tell SoapServer that it is fine when we feed it a SoapMediaHelper object instead of the regular input.
Before responding to a request for, in this case, mediaCollection, pass this response through the SoapMediaHelper. The SoapVarHelper will map all the properties=>value pairs as class properties, then the SoapMediaHelper will add attributes (also as name=>value pairs) to it.
SoapServer will take care of the rest.