Reputation: 564
I have the following code example which defines a Contact class that can hold a name and a list of phone numbers. What I want to be able to do is set an attribute of "primary" on all the numbers, where the value for it is '1' for one of them arbitrarily, and '0' for the rest.
I'd really like to do this using a class method but couldn't find a way to do this using a setter - is it possible somehow?
Here's my code. In it I:
<?php
// Symfony Serializer experiment
require $_SERVER['DOCUMENT_ROOT'].'/libraries/symfony/vendor/autoload.php';
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
Class Number {
private $Number;
private $Type;
// Getters
public function getNumber()
{
return $this->Number;
}
public function getType()
{
return $this->Type;
}
// Setters
public function setNumber($number)
{
$this->Number = $number;
return $this;
}
public function setType($type)
{
$this->Type = $type;
return $this;
}
}
class Contact
{
private $Name;
private $Numbers = array();
function __construct() {
${@Primary} = 0;
}
// Getters
public function getName()
{
return $this->Name;
}
public function getNumbers()
{
return $this->Numbers;
}
// Setters
public function setName($name)
{
$this->Name = $name;
return $this;
}
public function setNumbers($numbers)
{
$this->Numbers = $numbers;
return $this;
}
public function addNumber($number) {
$this->Numbers['Number'][] = $number;
}
}
// Serializer setup
$xmlEncoder = new XmlEncoder();
$xmlEncoder->setRootNodeName('Contact');
$encoders = array($xmlEncoder, new JsonEncoder());
$normalizers = array(new ObjectNormalizer());
$serializer = new Serializer($normalizers, $encoders);
// Create my object
$contact = new Contact();
$contact->setName('baa');
$number = new Number();
$number->setNumber('07878386123')->setType('Mobile');
$contact->addNumber($number);
// take a look at how it's stored
echo "<pre lang=xml>";
print_r($contact);
echo "</pre><br><br>";
// deserialize my desired xml into the same object
$data = <<<EOF
<Contact>
<Name>foo</Name>
<Numbers>
<Number primary='1'>
<Number>07878386459</Number>
<Type>Mobile</Type>
</Number>
<Number primary='0'>
<Number>02380860835</Number>
<Type>Landline</Type>
</Number>
</Numbers>
</Contact>
EOF;
$contact = $serializer->deserialize($data, Contact::class, 'xml', array('object_to_populate' => $contact));
// take a look at how it's stored
echo "<pre lang=xml>";
print_r($contact);
echo "</pre><br><br>";
// Add another number
$number = new Number();
$number->setNumber('02387345123')->setType('Landline');
$contact->addNumber($number);
// take a look at how it's stored
echo "<pre lang=xml>";
print_r($contact);
echo "</pre><br><br>";
// serialize to xml and see how the mixture of objects and arrays are treated
$xmlContent = $serializer->serialize($contact, 'xml');
$dom = new domDocument( '1.0', 'utf-8' );
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$result = $dom->loadXML($xmlContent);
$xmlContentFormatted=htmlentities($dom->saveXML());
?>
<pre lang=xml><?= $xmlContentFormatted ?></pre>
What I see in the output is:
2)
Contact Object
(
[Name:Contact:private] => baa
[Numbers:Contact:private] => Array
(
[Number] => Array
(
[0] => Number Object
(
[Number:Number:private] => 07878386123
[Type:Number:private] => Mobile
)
)
)
)
4)
Contact Object
(
[Name:Contact:private] => foo
[Numbers:Contact:private] => Array
(
[Number] => Array
(
[0] => Array
(
[@primary] => 1
[Number] => 07878386459
[Type] => Mobile
)
[1] => Array
(
[@primary] => 0
[Number] => 02380860835
[Type] => Landline
)
)
)
6) Mixture of objects and arrays. Could I code my Number class so that it uses an array rather than properties and have another method for setting attribute? (eg
$number->setNumber('07878386123')->setType('Mobile')->addAttr(array('primary' => '1'));
Contact Object
(
[Name:Contact:private] => foo
[Numbers:Contact:private] => Array
(
[Number] => Array
(
[0] => Array
(
[@primary] => 1
[Number] => 07878386459
[Type] => Mobile
)
[1] => Array
(
[@primary] => 0
[Number] => 02380860835
[Type] => Landline
)
[2] => Number Object
(
[Number:Number:private] => 02387345123
[Type:Number:private] => Landline
)
)
)
)
8)
<?xml version="1.0"?>
<Contact>
<Name>foo</Name>
<Numbers>
<Number primary="1">
<Number>07878386459</Number>
<Type>Mobile</Type>
</Number>
<Number primary="0">
<Number>02380860835</Number>
<Type>Landline</Type>
</Number>
<Number>
<Number>02387345123</Number>
<Type>Landline</Type>
</Number>
</Numbers>
</Contact>
When deserializing from XML that has tags that use attributes, the php objects use arrays, so it would appear that if you want to start out with a php representation that can be given attributes you need to use arrays.
Upvotes: 1
Views: 2198
Reputation: 564
Quite an unsophisticated hack, but managed to find a way to make it work. The modified example now does what I need:
<?php
// Symfony Serializer experiment
require $_SERVER['DOCUMENT_ROOT'].'/libraries/symfony/vendor/autoload.php';
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
// build datastructure as array
Class Number {
private $data;
public function __construct(array $attributes) {
$data = ['Number'];
foreach ($attributes as $name => $value) {
$this->data['Number']['@'.$name] = $value;
}
}
// Getters
public function getNumber()
{
return $this->data['Number']['Number'];
}
public function getType()
{
return $this->data['Number']['Type'];
}
public function get()
{
return $this->data['Number'];
}
// Setters
public function setNumber($number)
{
$this->data['Number']['Number'] = $number;
return $this;
}
public function setType($type)
{
$this->data['Number']['Type'] = $type;
return $this;
}
}
class Contact
{
private $data;
// Getters
public function getName()
{
return $this->data['Contact']['Name'];
}
public function getNumbers()
{
return $this->data['Contact']['Numbers'];
}
public function get()
{
return $this->data['Contact']; // this is the top level so return the Contact element, not data.
}
// Setters
public function setName($name)
{
$this->data['Contact']['Name'] = $name;
return $this;
}
public function setNumbers($numbers)
{
$this->data['Contact']['Numbers'] = $numbers;
return $this;
}
public function addNumber($number) {
$this->data['Contact']['Numbers']['Number'][] = $number;
}
}
// Serializer setup
$xmlEncoder = new XmlEncoder();
$xmlEncoder->setRootNodeName('Contact');
$encoders = array($xmlEncoder, new JsonEncoder());
$normalizers = array(new ObjectNormalizer());
$serializer = new Serializer($normalizers, $encoders);
$encoder = new \Symfony\Component\Serializer\Encoder\XmlEncoder();
// Create my object
$contact = (new Contact)->setName('Foo Bar');
$number = (new Number(array('primary'=>'1')))->setNumber('07878386123')->setType('Mobile');
$number_data = $number->get();
$contact->addNumber($number_data);
$number = (new Number(array('primary'=>'0')))->setNumber('02380876345')->setType('Landline');
$number_data = $number->get();
$contact->addNumber($number_data);
echo "<pre lang=xml>";
print_r($contact);
echo "</pre><br><br>";
$xmlContent = $serializer->serialize($contact->get(), 'xml');
$dom = new domDocument( '1.0', 'utf-8' );
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$result = $dom->loadXML($xmlContent);
$xmlContentFormatted=htmlentities($dom->saveXML());
?>
<pre lang=xml><?= $xmlContentFormatted ?></pre>
Output:
Contact Object
(
[data:Contact:private] => Array
(
[Contact] => Array
(
[Name] => Foo Bar
[Numbers] => Array
(
[Number] => Array
(
[0] => Array
(
[@primary] => 1
[Number] => 07878386123
[Type] => Mobile
)
[1] => Array
(
[@primary] => 0
[Number] => 02380876345
[Type] => Landline
)
)
)
)
)
)
<?xml version="1.0"?>
<Contact>
<Name>Foo Bar</Name>
<Numbers>
<Number primary="1">
<Number>07878386123</Number>
<Type>Mobile</Type>
</Number>
<Number primary="0">
<Number>02380876345</Number>
<Type>Landline</Type>
</Number>
</Numbers>
</Contact>
Upvotes: 2