Browse Source

Merge branch 'master' of https://github.com/Spea/JMSSerializerBundle

Johannes M. Schmitt 13 years ago
parent
commit
69b15778dd

+ 27 - 0
Annotation/XmlKeyValuePairs.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","METHOD"})
+ */
+final class XmlKeyValuePairs
+{
+}

+ 4 - 1
Metadata/Driver/AnnotationDriver.php

@@ -29,6 +29,7 @@ use JMS\SerializerBundle\Annotation\XmlRoot;
 use JMS\SerializerBundle\Annotation\XmlAttribute;
 use JMS\SerializerBundle\Annotation\XmlAttribute;
 use JMS\SerializerBundle\Annotation\XmlList;
 use JMS\SerializerBundle\Annotation\XmlList;
 use JMS\SerializerBundle\Annotation\XmlValue;
 use JMS\SerializerBundle\Annotation\XmlValue;
+use JMS\SerializerBundle\Annotation\XmlKeyValuePairs;
 use JMS\SerializerBundle\Annotation\PostSerialize;
 use JMS\SerializerBundle\Annotation\PostSerialize;
 use JMS\SerializerBundle\Annotation\PostDeserialize;
 use JMS\SerializerBundle\Annotation\PostDeserialize;
 use JMS\SerializerBundle\Annotation\PreSerialize;
 use JMS\SerializerBundle\Annotation\PreSerialize;
@@ -149,6 +150,8 @@ class AnnotationDriver implements DriverInterface
                         $propertyMetadata->xmlCollectionInline = $annot->inline;
                         $propertyMetadata->xmlCollectionInline = $annot->inline;
                         $propertyMetadata->xmlEntryName = $annot->entry;
                         $propertyMetadata->xmlEntryName = $annot->entry;
                         $propertyMetadata->xmlKeyAttribute = $annot->keyAttribute;
                         $propertyMetadata->xmlKeyAttribute = $annot->keyAttribute;
+                    } else if ($annot instanceof XmlKeyValuePairs) {
+                        $propertyMetadata->xmlKeyValuePairs = true;
                     } else if ($annot instanceof XmlAttribute) {
                     } else if ($annot instanceof XmlAttribute) {
                         $propertyMetadata->xmlAttribute = true;
                         $propertyMetadata->xmlAttribute = true;
                     } else if ($annot instanceof XmlValue) {
                     } else if ($annot instanceof XmlValue) {
@@ -177,4 +180,4 @@ class AnnotationDriver implements DriverInterface
 
 
         return $classMetadata;
         return $classMetadata;
     }
     }
-}
+}

+ 3 - 0
Metadata/PropertyMetadata.php

@@ -36,6 +36,7 @@ class PropertyMetadata extends BasePropertyMetadata
     public $xmlKeyAttribute;
     public $xmlKeyAttribute;
     public $xmlAttribute = false;
     public $xmlAttribute = false;
     public $xmlValue = false;
     public $xmlValue = false;
+    public $xmlKeyValuePairs = false;
     public $getter;
     public $getter;
     public $setter;
     public $setter;
     public $inline = false;
     public $inline = false;
@@ -83,6 +84,7 @@ class PropertyMetadata extends BasePropertyMetadata
             $this->xmlKeyAttribute,
             $this->xmlKeyAttribute,
             $this->xmlAttribute,
             $this->xmlAttribute,
             $this->xmlValue,
             $this->xmlValue,
+            $this->xmlKeyValuePairs,
             $this->getter,
             $this->getter,
             $this->setter,
             $this->setter,
             $this->inline,
             $this->inline,
@@ -105,6 +107,7 @@ class PropertyMetadata extends BasePropertyMetadata
             $this->xmlKeyAttribute,
             $this->xmlKeyAttribute,
             $this->xmlAttribute,
             $this->xmlAttribute,
             $this->xmlValue,
             $this->xmlValue,
+            $this->xmlKeyValuePairs,
             $this->getter,
             $this->getter,
             $this->setter,
             $this->setter,
             $this->inline,
             $this->inline,

+ 3 - 1
Metadata/VirtualPropertyMetadata.php

@@ -65,6 +65,7 @@ class VirtualPropertyMetadata extends PropertyMetadata
             $this->xmlKeyAttribute,
             $this->xmlKeyAttribute,
             $this->xmlAttribute,
             $this->xmlAttribute,
             $this->xmlValue,
             $this->xmlValue,
+            $this->xmlKeyValuePairs,
             $this->getter,
             $this->getter,
             $this->setter,
             $this->setter,
             $this->inline,
             $this->inline,
@@ -88,6 +89,7 @@ class VirtualPropertyMetadata extends PropertyMetadata
             $this->xmlKeyAttribute,
             $this->xmlKeyAttribute,
             $this->xmlAttribute,
             $this->xmlAttribute,
             $this->xmlValue,
             $this->xmlValue,
+            $this->xmlKeyValuePairs,
             $this->getter,
             $this->getter,
             $this->setter,
             $this->setter,
             $this->inline,
             $this->inline,
@@ -96,4 +98,4 @@ class VirtualPropertyMetadata extends PropertyMetadata
             $this->name
             $this->name
         ) = unserialize($str);
         ) = unserialize($str);
     }
     }
-}
+}

+ 29 - 21
Resources/doc/reference/annotations.rst

@@ -48,8 +48,8 @@ PHP's ``version_compare`` function.
 
 
 @Groups
 @Groups
 ~~~~~~~
 ~~~~~~~
-This annotation can be defined on a property to specifiy to if the property 
-should be serialized when only serializing specific groups (see 
+This annotation can be defined on a property to specifiy to if the property
+should be serialized when only serializing specific groups (see
 :doc:`../cookbook/exclusion_strategies`).
 :doc:`../cookbook/exclusion_strategies`).
 
 
 @AccessType
 @AccessType
@@ -66,12 +66,12 @@ set the value via reflection, but you may change this to use a public method ins
     class User
     class User
     {
     {
         private $name;
         private $name;
-        
+
         public function getName()
         public function getName()
         {
         {
             return $this->name;
             return $this->name;
         }
         }
-        
+
         public function setName($name)
         public function setName($name)
         {
         {
             $this->name = trim($name);
             $this->name = trim($name);
@@ -90,16 +90,16 @@ be called to retrieve, or set the value of the given property:
     class User
     class User
     {
     {
         private $id;
         private $id;
-        
+
         /** @Accessor(getter="getTrimmedName") */
         /** @Accessor(getter="getTrimmedName") */
         private $name;
         private $name;
-        
+
         // ...
         // ...
         public function getTrimmedName()
         public function getTrimmedName()
         {
         {
             return trim($this->name);
             return trim($this->name);
         }
         }
-        
+
         public function setName($name)
         public function setName($name)
         {
         {
             $this->name = $name;
             $this->name = $name;
@@ -108,17 +108,17 @@ be called to retrieve, or set the value of the given property:
 
 
 @AccessorOrder
 @AccessorOrder
 ~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~
-This annotation can be defined on a class to control the order of properties. By 
+This annotation can be defined on a class to control the order of properties. By
 default the order is undefined, but you may change it to either "alphabetical", or
 default the order is undefined, but you may change it to either "alphabetical", or
 "custom".
 "custom".
 
 
 .. code-block :: php
 .. code-block :: php
-    
+
     <?php
     <?php
 
 
-    /** 
-     * @AccessorOrder("alphabetical") 
-     * 
+    /**
+     * @AccessorOrder("alphabetical")
+     *
      * Resulting Property Order: id, name
      * Resulting Property Order: id, name
      */
      */
     class User
     class User
@@ -126,7 +126,7 @@ default the order is undefined, but you may change it to either "alphabetical",
         private $id;
         private $id;
         private $name;
         private $name;
     }
     }
-    
+
     /**
     /**
      * @AccessorOrder("custom", custom = {"name", "id"})
      * @AccessorOrder("custom", custom = {"name", "id"})
      *
      *
@@ -141,7 +141,7 @@ default the order is undefined, but you may change it to either "alphabetical",
 @Inline
 @Inline
 ~~~~~~~~
 ~~~~~~~~
 This annotation can be defined on a property to indicate that the data of the property
 This annotation can be defined on a property to indicate that the data of the property
-should be inlined. 
+should be inlined.
 
 
 **Note**: This only works for serialization, the serializer will not be able to deserialize
 **Note**: This only works for serialization, the serializer will not be able to deserialize
 objects with this annotation. Also, AccessorOrder will be using the name of the property
 objects with this annotation. Also, AccessorOrder will be using the name of the property
@@ -299,31 +299,31 @@ Resulting XML:
     <result id="1">
     <result id="1">
         <name><![CDATA[Johannes]]></name>
         <name><![CDATA[Johannes]]></name>
     </result>
     </result>
-    
+
 @XmlValue
 @XmlValue
 ~~~~~~~~~
 ~~~~~~~~~
 This allows you to mark properties which should be set as the value of the
 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 
+current element. Note that this has the limitation that any additional
 properties of that object must have the @XmlAttribute annotation.
 properties of that object must have the @XmlAttribute annotation.
 
 
 .. code-block :: php
 .. code-block :: php
 
 
     <?php
     <?php
-    
+
     use JMS\SerializerBundle\Annotation\XmlAttribute;
     use JMS\SerializerBundle\Annotation\XmlAttribute;
     use JMS\SerializerBundle\Annotation\XmlValue;
     use JMS\SerializerBundle\Annotation\XmlValue;
     use JMS\SerializerBundle\Annotation\XmlRoot;
     use JMS\SerializerBundle\Annotation\XmlRoot;
-    
+
     /** @XmlRoot("price") */
     /** @XmlRoot("price") */
     class Price
     class Price
     {
     {
         /** @XmlAttribute */
         /** @XmlAttribute */
         private $currency = 'EUR';
         private $currency = 'EUR';
-        
+
         /** @XmlValue */
         /** @XmlValue */
         private $amount = 1.23;
         private $amount = 1.23;
     }
     }
-    
+
 Resulting XML:
 Resulting XML:
 
 
 .. code-block :: xml
 .. code-block :: xml
@@ -380,4 +380,12 @@ Resulting XML:
 
 
 @XmlMap
 @XmlMap
 ~~~~~~~
 ~~~~~~~
-Similar to @XmlList, but the keys of the array are meaningful.
+Similar to @XmlList, but the keys of the array are meaningful.
+
+@XmlKeyValuePairs
+~~~~~~~~~~~~~~~~~
+This allows you to use the keys of an array as xml tags.
+
+.. note ::
+
+   When a key is an invalid xml tag name (e.g. 1_foo) the tag name *entry* will be used instead of the key.

+ 18 - 1
Serializer/XmlSerializationVisitor.php

@@ -114,7 +114,9 @@ class XmlSerializationVisitor extends AbstractSerializationVisitor
         $keyAttributeName = (null !== $this->currentMetadata && null !== $this->currentMetadata->xmlKeyAttribute) ? $this->currentMetadata->xmlKeyAttribute : null;
         $keyAttributeName = (null !== $this->currentMetadata && null !== $this->currentMetadata->xmlKeyAttribute) ? $this->currentMetadata->xmlKeyAttribute : null;
 
 
         foreach ($data as $k => $v) {
         foreach ($data as $k => $v) {
-            $entryNode = $this->document->createElement($entryName);
+            $tagName = (null !== $this->currentMetadata && $this->currentMetadata->xmlKeyValuePairs && $this->isElementNameValid($k)) ? $k : $entryName;
+
+            $entryNode = $this->document->createElement($tagName);
             $this->currentNode->appendChild($entryNode);
             $this->currentNode->appendChild($entryNode);
             $this->setCurrentNode($entryNode);
             $this->setCurrentNode($entryNode);
 
 
@@ -283,4 +285,19 @@ class XmlSerializationVisitor extends AbstractSerializationVisitor
 
 
         return $this->document->createTextNode((string) $data);
         return $this->document->createTextNode((string) $data);
     }
     }
+
+    /**
+     * Checks the name is a valid xml element name
+     *
+     * @param string $name
+     *
+     * @return Boolean
+     */
+    private function isElementNameValid($name)
+    {
+        return $name &&
+            false === strpos($name, ' ') &&
+            preg_match('#^[\pL_][\pL0-9._-]*$#ui', $name);
+    }
+
 }
 }

+ 30 - 0
Tests/Fixtures/ObjectWithXmlKeyValuePairs.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests\Fixtures;
+
+use JMS\SerializerBundle\Annotation\XmlKeyValuePairs;
+
+class ObjectWithXmlKeyValuePairs
+{
+    /**
+     * @var array
+     * @XmlKeyValuePairs
+     */
+    private $array = array(
+        'key-one' => 'foo',
+        'key-two' => 1,
+        'nested-array' => array(
+            'bar' => 'foo',
+        ),
+        'without-keys' => array(
+            1,
+            'test'
+        ),
+        'mixed' => array(
+            'test',
+            'foo' => 'bar',
+            '1_foo' => 'bar'
+        ),
+        1 => 'foo'
+    );
+}

+ 7 - 0
Tests/Serializer/XmlSerializationTest.php

@@ -26,6 +26,7 @@ use JMS\SerializerBundle\Tests\Fixtures\PersonCollection;
 use JMS\SerializerBundle\Tests\Fixtures\PersonLocation;
 use JMS\SerializerBundle\Tests\Fixtures\PersonLocation;
 use JMS\SerializerBundle\Tests\Fixtures\Person;
 use JMS\SerializerBundle\Tests\Fixtures\Person;
 use JMS\SerializerBundle\Tests\Fixtures\ObjectWithVirtualXmlProperties;
 use JMS\SerializerBundle\Tests\Fixtures\ObjectWithVirtualXmlProperties;
+use JMS\SerializerBundle\Tests\Fixtures\ObjectWithXmlKeyValuePairs;
 
 
 class XmlSerializationTest extends BaseSerializationTest
 class XmlSerializationTest extends BaseSerializationTest
 {
 {
@@ -102,6 +103,12 @@ class XmlSerializationTest extends BaseSerializationTest
         $this->assertEquals($this->getContent('virtual_properties_map'), $serializer->serialize(new ObjectWithVirtualXmlProperties(),'xml'));
         $this->assertEquals($this->getContent('virtual_properties_map'), $serializer->serialize(new ObjectWithVirtualXmlProperties(),'xml'));
     }
     }
 
 
+    public function testArrayKeyValues()
+    {
+        $serializer = $this->getSerializer();
+        $this->assertEquals($this->getContent('array_key_values'), $serializer->serialize(new ObjectWithXmlKeyValuePairs(), 'xml'));
+    }
+
     protected function getContent($key)
     protected function getContent($key)
     {
     {
         if (!file_exists($file = __DIR__.'/xml/'.$key.'.xml')) {
         if (!file_exists($file = __DIR__.'/xml/'.$key.'.xml')) {

+ 20 - 0
Tests/Serializer/xml/array_key_values.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<result>
+  <array>
+    <key-one><![CDATA[foo]]></key-one>
+    <key-two>1</key-two>
+    <nested-array>
+      <bar><![CDATA[foo]]></bar>
+    </nested-array>
+    <without-keys>
+      <entry>1</entry>
+      <entry><![CDATA[test]]></entry>
+    </without-keys>
+    <mixed>
+      <entry><![CDATA[test]]></entry>
+      <foo><![CDATA[bar]]></foo>
+      <entry><![CDATA[bar]]></entry>
+    </mixed>
+    <entry><![CDATA[foo]]></entry>
+  </array>
+</result>