Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ ChangeLog
1.3.0 (????-??-??)
------------------

* The `Service` class adds a new `mapValueObject` method which provides basic
capabilities to map between ValueObjects and XML.
* #61: You can now specify serializers for specific classes, allowing you
separate the object you want to serialize from the serializer. This uses the
`$classMap` property which is defined on both the `Service` and `Writer`.
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
"psr-4" : {
"Sabre\\Xml\\" : "lib/"
},
"files": ["lib/deserializers.php"]
"files": [
"lib/deserializers.php",
"lib/serializers.php"
]
},
"bin" : [
],
Expand Down
26 changes: 24 additions & 2 deletions lib/Service.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,30 @@ function write($rootElementName, $value, $contextUri = null) {

}

/**
* Maps the given value-object to the given rootElementName within the given namespace.
*
* The $rootElement must be specified in clark notation.
*
* The $className refernces a regular php class which must provide a __construct() without arguments.
* This methods creates instances of this class which later on picks up all xml child elements as public properties.
*
* @param string $rootElementName
* @param object $className
* @param string $namespace
*/
function mapValueObject($rootElementName, $className, $namespace) {
if (!class_exists($className)) {
throw new \InvalidArgumentException('class "' . $className . '" does not exist');
}

$this->elementMap['{' . $namespace . '}' . $rootElementName] = function(Reader $reader) use ($className, $namespace) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dropped the $this->namespaceMap[$namespace]=null expression here, because it had side effects.
I lead to all elements of this namespaces beeing represented by its localName instead of clark-notated (also for this not "registered" via mapValueObject).

After dropping the line we are still green, so nothing important ;).

see https://github.com/staabm/sabre-xml/commit/09836bbd21d093a7b23c792d31b9019730f13b49#diff-de76feda74829916defbb14d089c7c08R208 if you dont know what the hell I am talking about.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strike that. after I recognized that I looked at the wrong repos on travis I realised the line is still necessary.

therefore the question... what do you think about this sideeffect? any idea how to get arround it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused because this thread is on an 'outdated diff' discussing a line that wasn't in that diff... can you point me in the right direction?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return \Sabre\Xml\Deserializer\valueObject($reader, new $className(), $namespace);
};
$this->classMap[$className] = function(Writer $writer, $valueObject) use ($namespace) {
return \Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace);
};
}

/**
* Parses a clark-notation string, and returns the namespace and element
Expand All @@ -212,6 +236,4 @@ static function parseClarkNotation($str) {
];

}


}
28 changes: 28 additions & 0 deletions lib/deserializers.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,31 @@ function elementList(Reader $reader, $namespace = null) {
return $values;

}

/**
* @param Reader $reader
* @param object $valueObject
* @param string $namespace
* @return null|$valueObject
*/
function valueObject(Reader $reader, $valueObject, $namespace) {
if ($reader->isEmptyElement) {
$reader->next();
return null;
}

$reader->read();
do {

if ($reader->nodeType === Reader::ELEMENT && $reader->namespaceURI == $namespace) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future idea: add the ability to parse annotations and let people create classes like this:

class MyVO {

   /**
    * @map {http://namespace}elem
    */
   public $foo;

}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I also thought about such things... I dont like annotations that much because they add a magic layer which needs to be documented very properly. Also it mixes the serialize/deserialize logic into the ValueObjects which is the opposite direction this PR tries to aim for.

Maybe this will improve in php-src future in case annotations get a 1st class citizen in php.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having annotations also opens the door for mapping xml attributes somewhere. Atm we dont support them at all in this popo cases

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this function use use keyValue instead? It's one more layer of indirection, but it makes the implementation of popoObject a lot simpler (which can benefit people reading the source).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its one indirection and another short living temp. Structure.. In case you prefer it I am fine with it. We can restore to what we have right now in case it gets a perf bottleneck

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely. Thanks for your input.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually... I changed my mind!

I thought using keyValue this function would become super short, but that's not true... you would still need to loop through the result of keyValue and filter out the namespaced elements... which sucks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okidoki, you have 10 hours to change your mind again :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

time is up. pushed without the additional indirection ;)


$valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
} else {
$reader->read();
}
} while ($reader->nodeType !== Reader::END_ELEMENT);

$reader->read();

return $valueObject;
}
22 changes: 22 additions & 0 deletions lib/serializers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Sabre\Xml\Serializer;

use Sabre\Xml\Writer;

/**
* This file provides a number of 'serializer' helper functions.
* These can be used to easily specify custom serializers for specific
* PHP objects/values.
*/

/**
* @param Writer $writer
* @param object $valueObject
* @param string $namespace
*/
function valueObject(Writer $writer, $valueObject, $namespace) {
foreach (get_object_vars($valueObject) as $key => $val) {
$writer->writeElement('{' . $namespace . '}' . $key, $val);
}
}
57 changes: 57 additions & 0 deletions tests/Sabre/Xml/ServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,43 @@ function testWrite() {

}

function testMapValueObject() {

$input = <<<XML
<?xml version="1.0"?>
<order xmlns="http://sabredav.org/ns">
<id>1234</id>
<amount>99.99</amount>
<description>black friday deal</description>
<status>
<id>5</id>
<label>processed</label>
</status>
</order>

XML;

$ns = 'http://sabredav.org/ns';
$orderService = new \Sabre\Xml\Service();
$orderService->mapValueObject('order', 'Sabre\Xml\Order', $ns);
$orderService->mapValueObject('status', 'Sabre\Xml\OrderStatus', $ns);
$orderService->namespaceMap[$ns] = null;

$order = $orderService->parse($input);
$expected = new Order();
$expected->id = 1234;
$expected->amount = 99.99;
$expected->description = 'black friday deal';
$expected->status = new OrderStatus();
$expected->status->id = 5;
$expected->status->label = 'processed';

$this->assertEquals($expected, $order);

$writtenXml = $orderService->write('{http://sabredav.org/ns}order', $order);
$this->assertEquals($input, $writtenXml);
}

function testParseClarkNotation() {

$this->assertEquals([
Expand All @@ -217,3 +254,23 @@ function testParseClarkNotationFail() {
}

}

/**
* asset for testMapValueObject()
* @internal
*/
class Order {
public $id;
public $amount;
public $description;
public $status;
}

/**
* asset for testMapValueObject()
* @internal
*/
class OrderStatus {
public $id;
public $label;
}