Browse Source

Merge pull request #5919 from cakephp/issue-5542

Fix incorrect marshalling of _joinData.
Mark Story 11 years ago
parent
commit
b86407a383

+ 4 - 5
src/Database/Type.php

@@ -159,7 +159,7 @@ class Type
      */
     public function toDatabase($value, Driver $driver)
     {
-        return $this->_basicTypeCast($value, $driver);
+        return $this->_basicTypeCast($value);
     }
 
     /**
@@ -171,7 +171,7 @@ class Type
      */
     public function toPHP($value, Driver $driver)
     {
-        return $this->_basicTypeCast($value, $driver);
+        return $this->_basicTypeCast($value);
     }
 
     /**
@@ -179,10 +179,9 @@ class Type
      * If it is, returns converted value
      *
      * @param mixed $value value to be converted to PHP equivalent
-     * @param Driver $driver object from which database preferences and configuration will be extracted
      * @return mixed
      */
-    protected function _basicTypeCast($value, Driver $driver)
+    protected function _basicTypeCast($value)
     {
         if ($value === null) {
             return null;
@@ -259,6 +258,6 @@ class Type
      */
     public function marshal($value)
     {
-        return $value;
+        return $this->_basicTypeCast($value);
     }
 }

+ 1 - 1
src/ORM/AssociationsNormalizerTrait.php

@@ -31,7 +31,7 @@ trait AssociationsNormalizerTrait
     protected function _normalizeAssociations($associations)
     {
         $result = [];
-        foreach ($associations as $table => $options) {
+        foreach ((array)$associations as $table => $options) {
             $pointer =& $result;
 
             if (is_int($table)) {

+ 26 - 3
src/ORM/Marshaller.php

@@ -451,7 +451,6 @@ class Marshaller
             }
 
             $key = implode(';', $entity->extract($primary));
-
             if ($key === null || !isset($indexed[$key])) {
                 continue;
             }
@@ -521,7 +520,7 @@ class Marshaller
      * @param \Cake\ORM\Association $assoc The association to marshall
      * @param array $value The data to hydrate
      * @param array $options List of options.
-     * @return mixed
+     * @return array
      */
     protected function _mergeBelongsToMany($original, $assoc, $value, $options)
     {
@@ -538,8 +537,26 @@ class Marshaller
             return $this->mergeMany($original, $value, $options);
         }
 
+        return $this->_mergeJoinData($original, $assoc, $value, $options);
+    }
+
+    /**
+     * Merge the special _joinData property into the entity set.
+     *
+     * @param \Cake\Datasource\EntityInterface $original The original entity
+     * @param \Cake\ORM\Association $assoc The association to marshall
+     * @param array $value The data to hydrate
+     * @param array $options List of options.
+     * @return array An array of entities
+     */
+    protected function _mergeJoinData($original, $assoc, $value, $options)
+    {
+        $associated = isset($options['associated']) ? $options['associated'] : [];
         $extra = [];
         foreach ($original as $entity) {
+            // Mark joinData as accessible so we can marshal it properly.
+            $entity->accessible('_joinData', true);
+
             $joinData = $entity->get('_joinData');
             if ($joinData && $joinData instanceof EntityInterface) {
                 $extra[spl_object_hash($entity)] = $joinData;
@@ -558,8 +575,14 @@ class Marshaller
         foreach ($records as $record) {
             $hash = spl_object_hash($record);
             $value = $record->get('_joinData');
+
+            if (!is_array($value)) {
+                $record->unsetProperty('_joinData');
+                continue;
+            }
+
             if (isset($extra[$hash])) {
-                $record->set('_joinData', $marshaller->merge($extra[$hash], (array)$value, $nested));
+                $record->set('_joinData', $marshaller->merge($extra[$hash], $value, $nested));
             } else {
                 $joinData = $marshaller->one($value, $nested);
                 $record->set('_joinData', $joinData);

+ 21 - 0
tests/TestCase/Database/TypeTest.php

@@ -368,6 +368,27 @@ class TypeTest extends TestCase
     }
 
     /**
+     * Test marshalling booleans
+     *
+     * @return void
+     */
+    public function testBooleanMarshal()
+    {
+        $type = Type::build('boolean');
+        $this->assertTrue($type->marshal(true));
+        $this->assertTrue($type->marshal(1));
+        $this->assertTrue($type->marshal('1'));
+        $this->assertTrue($type->marshal('true'));
+
+        $this->assertFalse($type->marshal('false'));
+        $this->assertFalse($type->marshal('0'));
+        $this->assertFalse($type->marshal(0));
+        $this->assertFalse($type->marshal(''));
+        $this->assertFalse($type->marshal('invalid'));
+    }
+
+
+    /**
      * Tests uuid from database are converted correctly to PHP
      *
      * @return void

+ 72 - 2
tests/TestCase/ORM/MarshallerTest.php

@@ -52,7 +52,14 @@ class ProtectedArticle extends Entity
 class MarshallerTest extends TestCase
 {
 
-    public $fixtures = ['core.tags', 'core.articles_tags', 'core.articles', 'core.users', 'core.comments'];
+    public $fixtures = [
+        'core.tags',
+        'core.articles_tags',
+        'core.articles',
+        'core.users',
+        'core.comments',
+        'core.special_tags'
+    ];
 
     /**
      * setup
@@ -989,6 +996,69 @@ class MarshallerTest extends TestCase
     }
 
     /**
+     * Test that invalid _joinData (scalar data) is not marshalled.
+     *
+     * @return void
+     */
+    public function testMergeBelongsToManyJoinDataScalar()
+    {
+        TableRegistry::clear();
+        $articles = TableRegistry::get('Articles');
+        $articles->belongsToMany('Tags', [
+            'through' => 'SpecialTags'
+        ]);
+
+        $entity = $articles->get(1, ['contain' => 'Tags']);
+        $data = [
+            'title' => 'Haz data',
+            'tags' => [
+                ['id' => 3, 'tag' => 'Cake', '_joinData' => 'Invalid'],
+            ]
+        ];
+        $marshall = new Marshaller($articles);
+        $result = $marshall->merge($entity, $data, ['associated' => 'Tags._joinData']);
+
+        $articles->save($entity, ['associated' => ['Tags._joinData']]);
+        $this->assertFalse($entity->tags[0]->dirty('_joinData'));
+        $this->assertEmpty($entity->tags[0]->_joinData);
+    }
+
+    /**
+     * Test merging the _joinData entity for belongstomany associations when * is not
+     * accessible.
+     *
+     * @return void
+     */
+    public function testMergeBelongsToManyJoinDataNotAccessible()
+    {
+        TableRegistry::clear();
+        $articles = TableRegistry::get('Articles');
+        $articles->belongsToMany('Tags', [
+            'through' => 'SpecialTags'
+        ]);
+
+        $entity = $articles->get(1, ['contain' => 'Tags']);
+        $data = [
+            'title' => 'Haz data',
+            'tags' => [
+                ['id' => 3, 'tag' => 'Cake', '_joinData' => ['highlighted' => '1', 'author_id' => '99']],
+            ]
+        ];
+        // Make only specific fields accessible, but not _joinData.
+        $entity->tags[0]->accessible('*', false);
+        $entity->tags[0]->accessible(['article_id', 'tag_id'], true);
+
+        $marshall = new Marshaller($articles);
+        $result = $marshall->merge($entity, $data, ['associated' => 'Tags._joinData']);
+
+        $this->assertTrue($entity->tags[0]->dirty('_joinData'));
+        $this->assertTrue($result->tags[0]->_joinData->dirty('author_id'), 'Field not modified');
+        $this->assertTrue($result->tags[0]->_joinData->dirty('highlighted'), 'Field not modified');
+        $this->assertSame(99, $result->tags[0]->_joinData->author_id);
+        $this->assertTrue($result->tags[0]->_joinData->highlighted);
+    }
+
+    /**
      * Test merging the _joinData entity for belongstomany associations.
      *
      * @return void
@@ -1017,7 +1087,7 @@ class MarshallerTest extends TestCase
             ],
         ];
 
-        $options = ['associated' => ['Tags' => ['associated' => ['_joinData']]]];
+        $options = ['associated' => ['Tags._joinData']];
         $marshall = new Marshaller($this->articles);
         $entity = $marshall->one($data, $options);
         $entity->accessible('*', true);