Commit 42f741dd authored by Katherine Walker's avatar Katherine Walker

Merge pull request #524

parents 19a74922 ffe7c65b
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
* Copyright 2016-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -32,6 +32,16 @@ use JsonSerializable;
*/
class BSONArray extends ArrayObject implements JsonSerializable, Serializable, Unserializable
{
/**
* Clone this BSONArray.
*/
public function __clone()
{
foreach ($this as $key => $value) {
$this[$key] = \MongoDB\recursive_copy($value);
}
}
/**
* Factory method for var_export().
*
......
<?php
/*
* Copyright 2016-2017 MongoDB, Inc.
* Copyright 2016-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -32,6 +32,16 @@ use JsonSerializable;
*/
class BSONDocument extends ArrayObject implements JsonSerializable, Serializable, Unserializable
{
/**
* Deep clone this BSONDocument.
*/
public function __clone()
{
foreach ($this as $key => $value) {
$this[$key] = \MongoDB\recursive_copy($value);
}
}
/**
* Constructor.
*
......
......@@ -194,3 +194,27 @@ function is_string_array($input) {
return true;
}
/**
* Performs a deep copy of a value.
*
* This function will clone objects and recursively copy values within arrays.
*
* @internal
* @see https://bugs.php.net/bug.php?id=49664
* @param mixed $element Value to be copied
* @return mixed
*/
function recursive_copy($element) {
if (is_array($element)) {
foreach ($element as $key => $value) {
$element[$key] = recursive_copy($value);
}
return $element;
}
if ( ! is_object($element)) {
return $element;
}
return clone $element;
}
......@@ -6,11 +6,7 @@ use MongoDB\Driver\Command;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use InvalidArgumentException;
use stdClass;
use Traversable;
use UnexpectedValueException;
abstract class FunctionalTestCase extends TestCase
......@@ -49,34 +45,6 @@ abstract class FunctionalTestCase extends TestCase
$this->assertEquals((string) $expectedObjectId, (string) $actualObjectId);
}
protected function assertSameDocument($expectedDocument, $actualDocument)
{
$this->assertEquals(
\MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP($this->normalizeBSON($expectedDocument))),
\MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP($this->normalizeBSON($actualDocument)))
);
}
protected function assertSameDocuments(array $expectedDocuments, $actualDocuments)
{
if ($actualDocuments instanceof Traversable) {
$actualDocuments = iterator_to_array($actualDocuments);
}
if ( ! is_array($actualDocuments)) {
throw new InvalidArgumentException('$actualDocuments is not an array or Traversable');
}
$normalizeRootDocuments = function($document) {
return \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP($this->normalizeBSON($document)));
};
$this->assertEquals(
array_map($normalizeRootDocuments, $expectedDocuments),
array_map($normalizeRootDocuments, $actualDocuments)
);
}
protected function getFeatureCompatibilityVersion(ReadPreference $readPreference = null)
{
if (version_compare($this->getServerVersion(), '3.4.0', '<')) {
......@@ -127,48 +95,4 @@ abstract class FunctionalTestCase extends TestCase
throw new UnexpectedValueException('Could not determine server version');
}
/**
* Normalizes a BSON document or array for use with assertEquals().
*
* The argument will be converted to a BSONArray or BSONDocument based on
* its type and keys. Document fields will be sorted alphabetically. Each
* value within the array or document will then be normalized recursively.
*
* @param array|object $bson
* @return BSONDocument|BSONArray
* @throws InvalidArgumentException if $bson is not an array or object
*/
private function normalizeBSON($bson)
{
if ( ! is_array($bson) && ! is_object($bson)) {
throw new InvalidArgumentException('$bson is not an array or object');
}
if ($bson instanceof BSONArray || (is_array($bson) && $bson === array_values($bson))) {
if ( ! $bson instanceof BSONArray) {
$bson = new BSONArray($bson);
}
} else {
if ( ! $bson instanceof BSONDocument) {
$bson = new BSONDocument((array) $bson);
}
$bson->ksort();
}
foreach ($bson as $key => $value) {
if ($value instanceof BSONArray || (is_array($value) && $value === array_values($value))) {
$bson[$key] = $this->normalizeBSON($value);
continue;
}
if ($value instanceof stdClass || $value instanceof BSONDocument || is_array($value)) {
$bson[$key] = $this->normalizeBSON($value);
continue;
}
}
return $bson;
}
}
......@@ -5,6 +5,7 @@ namespace MongoDB\Tests\Model;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Tests\TestCase;
use stdClass;
class BSONArrayTest extends TestCase
{
......@@ -17,6 +18,31 @@ class BSONArrayTest extends TestCase
$this->assertSame(['foo', 'bar'], $array->bsonSerialize());
}
public function testClone()
{
$array = new BSONArray([
[
'foo',
new stdClass,
['bar', new stdClass],
],
new BSONArray([
'foo',
new stdClass,
['bar', new stdClass],
]),
]);
$arrayClone = clone $array;
$this->assertSameDocument($array, $arrayClone);
$this->assertNotSame($array, $arrayClone);
$this->assertNotSame($array[0][1], $arrayClone[0][1]);
$this->assertNotSame($array[0][2][1], $arrayClone[0][2][1]);
$this->assertNotSame($array[1], $arrayClone[1]);
$this->assertNotSame($array[1][1], $arrayClone[1][1]);
$this->assertNotSame($array[1][2][1], $arrayClone[1][2][1]);
}
public function testJsonSerialize()
{
$document = new BSONArray([
......
......@@ -6,6 +6,7 @@ use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Tests\TestCase;
use ArrayObject;
use stdClass;
class BSONDocumentTest extends TestCase
{
......@@ -25,6 +26,31 @@ class BSONDocumentTest extends TestCase
$this->assertEquals((object) [0 => 'foo', 2 => 'bar'], $document->bsonSerialize());
}
public function testClone()
{
$document = new BSONDocument([
'a' => [
'a' => 'foo',
'b' => new stdClass,
'c' => ['bar', new stdClass],
],
'b' => new BSONDocument([
'a' => 'foo',
'b' => new stdClass,
'c' => ['bar', new stdClass],
]),
]);
$documentClone = clone $document;
$this->assertSameDocument($document, $documentClone);
$this->assertNotSame($document, $documentClone);
$this->assertNotSame($document['a']['b'], $documentClone['a']['b']);
$this->assertNotSame($document['a']['c'][1], $documentClone['a']['c'][1]);
$this->assertNotSame($document['b'], $documentClone['b']);
$this->assertNotSame($document['b']['b'], $documentClone['b']['b']);
$this->assertNotSame($document['b']['c'][1], $documentClone['b']['c'][1]);
}
public function testJsonSerialize()
{
$document = new BSONDocument([
......
......@@ -5,9 +5,13 @@ namespace MongoDB\Tests;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use PHPUnit\Framework\TestCase as BaseTestCase;
use InvalidArgumentException;
use ReflectionClass;
use stdClass;
use Traversable;
abstract class TestCase extends BaseTestCase
{
......@@ -48,6 +52,34 @@ abstract class TestCase extends BaseTestCase
return $this->wrapValuesForDataProvider($this->getInvalidDocumentValues());
}
protected function assertSameDocument($expectedDocument, $actualDocument)
{
$this->assertEquals(
\MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP($this->normalizeBSON($expectedDocument))),
\MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP($this->normalizeBSON($actualDocument)))
);
}
protected function assertSameDocuments(array $expectedDocuments, $actualDocuments)
{
if ($actualDocuments instanceof Traversable) {
$actualDocuments = iterator_to_array($actualDocuments);
}
if ( ! is_array($actualDocuments)) {
throw new InvalidArgumentException('$actualDocuments is not an array or Traversable');
}
$normalizeRootDocuments = function($document) {
return \MongoDB\BSON\toJSON(\MongoDB\BSON\fromPHP($this->normalizeBSON($document)));
};
$this->assertEquals(
array_map($normalizeRootDocuments, $expectedDocuments),
array_map($normalizeRootDocuments, $actualDocuments)
);
}
/**
* Return the test collection name.
*
......@@ -190,4 +222,48 @@ abstract class TestCase extends BaseTestCase
{
return array_map(function($value) { return [$value]; }, $values);
}
/**
* Normalizes a BSON document or array for use with assertEquals().
*
* The argument will be converted to a BSONArray or BSONDocument based on
* its type and keys. Document fields will be sorted alphabetically. Each
* value within the array or document will then be normalized recursively.
*
* @param array|object $bson
* @return BSONDocument|BSONArray
* @throws InvalidArgumentException if $bson is not an array or object
*/
private function normalizeBSON($bson)
{
if ( ! is_array($bson) && ! is_object($bson)) {
throw new InvalidArgumentException('$bson is not an array or object');
}
if ($bson instanceof BSONArray || (is_array($bson) && $bson === array_values($bson))) {
if ( ! $bson instanceof BSONArray) {
$bson = new BSONArray($bson);
}
} else {
if ( ! $bson instanceof BSONDocument) {
$bson = new BSONDocument((array) $bson);
}
$bson->ksort();
}
foreach ($bson as $key => $value) {
if ($value instanceof BSONArray || (is_array($value) && $value === array_values($value))) {
$bson[$key] = $this->normalizeBSON($value);
continue;
}
if ($value instanceof stdClass || $value instanceof BSONDocument || is_array($value)) {
$bson[$key] = $this->normalizeBSON($value);
continue;
}
}
return $bson;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment