Procházet zdrojové kódy

Merge branch 'xmlValue'

Johannes Schmitt před 13 roky
rodič
revize
e2b32487c8

+ 27 - 0
Annotation/XmlValue.php

@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * Copyright 2011 Johannes M. Schmitt <schmittjoh@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace JMS\SerializerBundle\Annotation;
+
+/**
+ * @Annotation
+ * @Target("PROPERTY")
+ */
+final class XmlValue
+{
+}

+ 3 - 0
Metadata/Driver/AnnotationDriver.php

@@ -22,6 +22,7 @@ use JMS\SerializerBundle\Annotation\XmlMap;
 use JMS\SerializerBundle\Annotation\XmlRoot;
 use JMS\SerializerBundle\Annotation\XmlAttribute;
 use JMS\SerializerBundle\Annotation\XmlList;
+use JMS\SerializerBundle\Annotation\XmlValue;
 use JMS\SerializerBundle\Annotation\PostSerialize;
 use JMS\SerializerBundle\Annotation\PostDeserialize;
 use JMS\SerializerBundle\Annotation\PreSerialize;
@@ -96,6 +97,8 @@ class AnnotationDriver implements DriverInterface
                         $propertyMetadata->xmlKeyAttribute = $annot->keyAttribute;
                     } else if ($annot instanceof XmlAttribute) {
                         $propertyMetadata->xmlAttribute = true;
+                    } else if ($annot instanceof XmlValue) {
+                        $propertyMetadata->xmlValue = true;
                     }
                 }
 

+ 4 - 0
Metadata/Driver/XmlDriver.php

@@ -126,6 +126,10 @@ class XmlDriver extends AbstractFileDriver
                     if (isset($pElem->attributes()->{'xml-attribute'})) {
                         $pMetadata->xmlAttribute = 'true' === (string) $pElem->attributes()->{'xml-attribute'};
                     }
+                    
+                    if (isset($pElem->attributes()->{'xml-value'})) {
+                        $pMetadata->xmlValue = 'true' === (string) $pElem->attributes()->{'xml-value'};
+                    }
 
                     if ((ExclusionPolicy::NONE === (string)$exclusionPolicy && !$isExclude)
                         || (ExclusionPolicy::ALL === (string)$exclusionPolicy && $isExpose)) {

+ 4 - 0
Metadata/Driver/YamlDriver.php

@@ -115,6 +115,10 @@ class YamlDriver extends AbstractFileDriver
                     if (isset($pConfig['xml_attribute'])) {
                         $pMetadata->xmlAttribute = (Boolean) $pConfig['xml_attribute'];
                     }
+                    
+                    if (isset($pConfig['xml_value'])) {
+                        $pMetadata->xmlValue = (Boolean) $pConfig['xml_value'];
+                    }
 
                     if ((ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
                     || (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)) {

+ 3 - 0
Metadata/PropertyMetadata.php

@@ -31,6 +31,7 @@ class PropertyMetadata extends BasePropertyMetadata
     public $xmlEntryName;
     public $xmlKeyAttribute;
     public $xmlAttribute = false;
+    public $xmlValue = false;
 
     public function serialize()
     {
@@ -44,6 +45,7 @@ class PropertyMetadata extends BasePropertyMetadata
             $this->xmlEntryName,
             $this->xmlKeyAttribute,
             $this->xmlAttribute,
+            $this->xmlValue,
             parent::serialize(),
         ));
     }
@@ -60,6 +62,7 @@ class PropertyMetadata extends BasePropertyMetadata
             $this->xmlEntryName,
             $this->xmlKeyAttribute,
             $this->xmlAttribute,
+            $this->xmlValue,
             $parentStr
         ) = unserialize($str);
 

+ 28 - 0
Resources/doc/index.rst

@@ -360,6 +360,34 @@ Resulting XML::
     <result id="1">
         <name><![CDATA[Johannes]]></name>
     </result>
+    
+@XmlValue
+~~~~~~~~~
+This allows you to mark properties which should be set as the value of the
+current element. Note that this has the limitation that any additional 
+properties of that object must have the @XmlAttribute annotation.
+
+::
+
+    <?php
+    
+    use JMS\SerializerBundle\Annotation\XmlAttribute;
+    use JMS\SerializerBundle\Annotation\XmlValue;
+    use JMS\SerializerBundle\Annotation\XmlRoot;
+    
+    /** @XmlRoot("price") */
+    class Price
+    {
+        /** @XmlAttribute */
+        private $currency = 'EUR';
+        
+        /** @XmlValue */
+        private $amount = 1.23;
+    }
+    
+Resulting XML::
+
+    <price currency="EUR">1.23</price>
 
 @XmlList
 ~~~~~~~~

+ 7 - 0
Serializer/XmlDeserializationVisitor.php

@@ -205,6 +205,13 @@ class XmlDeserializationVisitor extends AbstractDeserializationVisitor
 
             return;
         }
+        
+        if ($metadata->xmlValue) {
+            $v = $this->navigator->accept($data, $metadata->type, $this);
+            $metadata->reflection->setValue($this->currentObject, $v);
+
+            return;
+        }
 
         if ($metadata->xmlCollection) {
             $enclosingElem = $data;

+ 21 - 0
Serializer/XmlSerializationVisitor.php

@@ -40,6 +40,7 @@ class XmlSerializationVisitor extends AbstractSerializationVisitor
     private $metadataStack;
     private $currentNode;
     private $currentMetadata;
+    private $hasValue;
 
     public function setDefaultRootName($name)
     {
@@ -140,6 +141,8 @@ class XmlSerializationVisitor extends AbstractSerializationVisitor
             $this->document = $this->createDocument(null, null, false);
             $this->document->appendChild($this->currentNode = $this->document->createElement($metadata->xmlRootName ?: $this->defaultRootName));
         }
+
+        $this->hasValue = false;
     }
 
     public function visitProperty(PropertyMetadata $metadata, $object)
@@ -159,6 +162,24 @@ class XmlSerializationVisitor extends AbstractSerializationVisitor
             return;
         }
 
+        if (($metadata->xmlValue && $this->currentNode->childNodes->length > 0)
+            || (!$metadata->xmlValue && $this->hasValue)) {
+            throw new \RuntimeException(sprintf('If you make use of @XmlValue, all other properties in the class must have the @XmlAttribute annotation. Invalid usage detected in class %s.', $metadata->reflection->class));
+        }
+
+        if ($metadata->xmlValue) {
+            $this->hasValue = true;
+
+            $node = $this->navigator->accept($v, null, $this);
+            if (!$node instanceof \DOMCharacterData) {
+                throw new RuntimeException('Unsupported value for property %s::$%s. Expected character data, but got %s.', $metadata->reflection->class, $metadata->reflection->name, is_object($node) ? get_class($node) : gettype($node));
+            }
+
+            $this->currentNode->appendChild($node);
+
+            return;
+        }
+
         if ($addEnclosingElement = !$metadata->xmlCollection || !$metadata->xmlCollectionInline) {
             $element = $this->document->createElement($this->namingStrategy->translateName($metadata));
             $this->setCurrentNode($element);

+ 18 - 0
Tests/Fixtures/CurrencyAwareOrder.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Fixtures;
+
+use JMS\SerializerBundle\Annotation\XmlRoot;
+use JMS\SerializerBundle\Annotation\Type;
+
+/** @XmlRoot("order") */
+class CurrencyAwareOrder
+{
+    /** @Type("JMS\SerializerBundle\Tests\Fixtures\CurrencyAwarePrice") */
+    private $cost;
+
+    public function __construct(CurrencyAwarePrice $price = null)
+    {
+        $this->cost = $price ?: new CurrencyAwarePrice(5);
+    }
+}

+ 27 - 0
Tests/Fixtures/CurrencyAwarePrice.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Fixtures;
+
+use JMS\SerializerBundle\Annotation as Serializer;
+
+/** @Serializer\XmlRoot("price") */
+class CurrencyAwarePrice
+{
+    /**
+     * @Serializer\XmlAttribute
+     * @Serializer\Type("string")
+     */
+    private $currency;
+
+    /**
+     * @Serializer\XmlValue
+     * @Serializer\Type("double")
+     */
+    private $amount;
+
+    public function __construct($amount, $currency = 'EUR')
+    {
+        $this->currency = $currency;
+        $this->amount = $amount;
+    }
+}

+ 13 - 0
Tests/Fixtures/InvalidUsageOfXmlValue.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Fixtures;
+
+use JMS\SerializerBundle\Annotation\XmlValue;
+
+class InvalidUsageOfXmlValue
+{
+    /** @XmlValue */
+    private $value = 'bar';
+
+    private $element = 'foo';
+}

+ 18 - 0
Tests/Fixtures/Order.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Fixtures;
+
+use JMS\SerializerBundle\Annotation\XmlRoot;
+use JMS\SerializerBundle\Annotation\Type;
+
+/** @XmlRoot("order") */
+class Order
+{
+    /** @Type("JMS\SerializerBundle\Tests\Fixtures\Price") */
+    private $cost;
+
+    public function __construct(Price $price = null)
+    {
+        $this->cost = $price ?: new Price(5);
+    }
+}

+ 40 - 0
Tests/Fixtures/Price.php

@@ -0,0 +1,40 @@
+<?php
+
+/*
+ * Copyright 2011 Johannes M. Schmitt <schmittjoh@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace JMS\SerializerBundle\Tests\Fixtures;
+
+use JMS\SerializerBundle\Annotation\Type;
+use JMS\SerializerBundle\Annotation\XmlValue;
+use JMS\SerializerBundle\Annotation\XmlRoot;
+
+/**
+ * @XmlRoot("price")
+ */
+class Price
+{
+    /**
+     * @Type("double")
+     * @XmlValue
+     */
+    private $price;
+
+    function __construct($price)
+    {
+        $this->price = $price;
+    }
+}

+ 8 - 0
Tests/Metadata/Driver/BaseDriverTest.php

@@ -55,6 +55,14 @@ abstract class BaseDriverTest extends \PHPUnit_Framework_TestCase
         $p = new PropertyMetadata($m->name, 'author');
         $p->type = 'JMS\SerializerBundle\Tests\Fixtures\Author';
         $this->assertEquals($p, $m->propertyMetadata['author']);
+        
+        $m = $this->getDriver()->loadMetadataForClass(new \ReflectionClass('JMS\SerializerBundle\Tests\Fixtures\Price'));
+        $this->assertNotNull($m);
+        
+        $p = new PropertyMetadata($m->name, 'price');
+        $p->type = 'double';
+        $p->xmlValue = true;
+        $this->assertEquals($p, $m->propertyMetadata['price']);
     }
 
     abstract protected function getDriver();

+ 13 - 0
Tests/Metadata/Driver/php/Price.php

@@ -0,0 +1,13 @@
+<?php
+
+use JMS\SerializerBundle\Metadata\ClassMetadata;
+use JMS\SerializerBundle\Metadata\PropertyMetadata;
+
+$metadata = new ClassMetadata('JMS\SerializerBundle\Tests\Fixtures\Price');
+
+$pMetadata = new PropertyMetadata('JMS\SerializerBundle\Tests\Fixtures\Price', 'price');
+$pMetadata->type = 'double';
+$pMetadata->xmlValue = true;
+$metadata->addPropertyMetadata($pMetadata);
+
+return $metadata;

+ 6 - 0
Tests/Metadata/Driver/xml/Price.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<serializer>
+    <class name="JMS\SerializerBundle\Tests\Fixtures\Price">
+        <property name="price" xml-value="true" type="double" />
+    </class>
+</serializer>

+ 5 - 0
Tests/Metadata/Driver/yml/Price.yml

@@ -0,0 +1,5 @@
+JMS\SerializerBundle\Tests\Fixtures\Price:
+    properties:
+        price:
+            type: double
+            xml_value: true

+ 48 - 0
Tests/Serializer/BaseSerializationTest.php

@@ -18,6 +18,12 @@
 
 namespace JMS\SerializerBundle\Tests\Serializer;
 
+use JMS\SerializerBundle\Tests\Fixtures\CurrencyAwareOrder;
+
+use JMS\SerializerBundle\Tests\Fixtures\CurrencyAwarePrice;
+
+use JMS\SerializerBundle\Tests\Fixtures\Order;
+
 use Symfony\Component\Yaml\Inline;
 use JMS\SerializerBundle\Serializer\YamlSerializationVisitor;
 use Doctrine\Common\Collections\ArrayCollection;
@@ -48,6 +54,7 @@ use Doctrine\Common\Annotations\AnnotationReader;
 use JMS\SerializerBundle\Metadata\Driver\AnnotationDriver;
 use Metadata\MetadataFactory;
 use JMS\SerializerBundle\Tests\Fixtures\SimpleObject;
+use JMS\SerializerBundle\Tests\Fixtures\Price;
 use JMS\SerializerBundle\Serializer\Naming\CamelCaseNamingStrategy;
 use JMS\SerializerBundle\Serializer\Naming\SerializedNameAnnotationStrategy;
 use JMS\SerializerBundle\Serializer\JsonSerializationVisitor;
@@ -183,6 +190,47 @@ abstract class BaseSerializationTest extends \PHPUnit_Framework_TestCase
         }
     }
 
+    public function testPrice()
+    {
+        $price = new Price(3);
+        $this->assertEquals($this->getContent('price'), $this->serialize($price));
+
+        if ($this->hasDeserializer()) {
+            $deserialized = $this->deserialize($this->getContent('price'), get_class($price));
+            $this->assertEquals(3, $this->getField($deserialized, 'price'));
+        }
+    }
+
+    public function testOrder()
+    {
+        $order = new Order(new Price(12.34));
+        $this->assertEquals($this->getContent('order'), $this->serialize($order));
+
+        if ($this->hasDeserializer()) {
+            $this->assertEquals($order, $this->deserialize($this->getContent('order'), get_class($order)));
+        }
+    }
+
+    public function testCurrencyAwarePrice()
+    {
+        $price = new CurrencyAwarePrice(2.34);
+        $this->assertEquals($this->getContent('currency_aware_price'), $this->serialize($price));
+
+        if ($this->hasDeserializer()) {
+            $this->assertEquals($price, $this->deserialize($this->getContent('currency_aware_price'), get_class($price)));
+        }
+    }
+
+    public function testOrderWithCurrencyAwarePrice()
+    {
+        $order = new CurrencyAwareOrder(new CurrencyAwarePrice(1.23));
+        $this->assertEquals($this->getContent('order_with_currency_aware_price'), $this->serialize($order));
+
+        if ($this->hasDeserializer()) {
+            $this->assertEquals($order, $this->deserialize($this->getContent('order_with_currency_aware_price'), get_class($order)));
+        }
+    }
+
     public function testArticle()
     {
         $article = new Article();

+ 4 - 0
Tests/Serializer/JsonSerializationTest.php

@@ -43,6 +43,10 @@ class JsonSerializationTest extends BaseSerializationTest
             $outputs['array_objects'] = '[{"foo":"foo","moo":"bar","camel_case":"boo"},{"foo":"baz","moo":"boo","camel_case":"boo"}]';
             $outputs['array_mixed'] = '["foo",1,true,{"foo":"foo","moo":"bar","camel_case":"boo"},[1,3,true]]';
             $outputs['blog_post'] = '{"title":"This is a nice title.","created_at":"2011-07-30T00:00:00+0000","is_published":false,"comments":[{"author":{"full_name":"Foo Bar"},"text":"foo"}],"author":{"full_name":"Foo Bar"}}';
+            $outputs['price'] = '{"price":3}';
+            $outputs['currency_aware_price'] = '{"currency":"EUR","amount":2.34}';
+            $outputs['order'] = '{"cost":{"price":12.34}}';
+            $outputs['order_with_currency_aware_price'] = '{"cost":{"currency":"EUR","amount":1.23}}';
             $outputs['log'] = '{"author_list":[{"full_name":"Johannes Schmitt"},{"full_name":"John Doe"}],"comments":[{"author":{"full_name":"Foo Bar"},"text":"foo"},{"author":{"full_name":"Foo Bar"},"text":"bar"},{"author":{"full_name":"Foo Bar"},"text":"baz"}]}';
             $outputs['lifecycle_callbacks'] = '{"name":"Foo Bar"}';
             $outputs['form_errors'] = '["This is the form error","Another error"]';

+ 10 - 0
Tests/Serializer/XmlSerializationTest.php

@@ -18,10 +18,20 @@
 
 namespace JMS\SerializerBundle\Tests\Serializer;
 
+use JMS\SerializerBundle\Tests\Fixtures\InvalidUsageOfXmlValue;
 use JMS\SerializerBundle\Exception\InvalidArgumentException;
 
 class XmlSerializationTest extends BaseSerializationTest
 {
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testInvalidUsageOfXmlValue()
+    {
+        $obj = new InvalidUsageOfXmlValue();
+        $this->serialize($obj);
+    }
+
     protected function getContent($key)
     {
         if (!file_exists($file = __DIR__.'/xml/'.$key.'.xml')) {

+ 2 - 0
Tests/Serializer/xml/currency_aware_price.xml

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<price currency="EUR">2.34</price>

+ 4 - 0
Tests/Serializer/xml/order.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<order>
+  <cost>12.34</cost>
+</order>

+ 4 - 0
Tests/Serializer/xml/order_with_currency_aware_price.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<order>
+  <cost currency="EUR">1.23</cost>
+</order>

+ 2 - 0
Tests/Serializer/xml/price.xml

@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<price>3</price>

+ 2 - 0
Tests/Serializer/yml/currency_aware_price.yml

@@ -0,0 +1,2 @@
+currency: EUR
+amount: 2.34

+ 2 - 0
Tests/Serializer/yml/order.yml

@@ -0,0 +1,2 @@
+cost:
+    price: 12.34

+ 3 - 0
Tests/Serializer/yml/order_with_currency_aware_price.yml

@@ -0,0 +1,3 @@
+cost:
+    currency: EUR
+    amount: 1.23

+ 1 - 0
Tests/Serializer/yml/price.yml

@@ -0,0 +1 @@
+price: 3