Skip to content

Commit

Permalink
Merge 1.1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
malarzm committed Jan 14, 2017
2 parents 00cd4de + 12611cf commit 1386959
Show file tree
Hide file tree
Showing 14 changed files with 362 additions and 23 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG-1.1.md
Expand Up @@ -5,6 +5,18 @@ This changelog references the relevant changes done in 1.1 minor versions. If up
1.0.x branch, please review
[Upgrade Path](https://github.com/doctrine/mongodb-odm/blob/master/CHANGELOG-1.1.md#upgrade-path).

1.1.3 (2017-01-14)
------------------

All issues and pull requests in this release may be found under the
[1.1.3 milestone](https://github.com/doctrine/mongodb-odm/issues?q=milestone%3A1.1.3).

* [#1551](https://github.com/doctrine/mongodb-odm/pull/1551) fixes modifying reference fields with Query Builder
* [#1550](https://github.com/doctrine/mongodb-odm/pull/1550) fixes reusing embedded documents with multiple flushes
* [#1543](https://github.com/doctrine/mongodb-odm/pull/1543) provides support for nullable return types in custom collections
* [#1540](https://github.com/doctrine/mongodb-odm/pull/1540) ensures compatibility with MongoDB 3.4
* [#1544](https://github.com/doctrine/mongodb-odm/pull/1544) ensures compatibility with PHP 7.1

1.1.2 (2016-10-07)
------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/en/conf.py
Expand Up @@ -47,7 +47,7 @@
# The short X.Y version.
version = '1.1'
# The full version, including alpha/beta/rc tags.
release = '1.1.2'
release = '1.1.3'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
9 changes: 8 additions & 1 deletion docs/en/reference/query-builder-api.rst
Expand Up @@ -665,7 +665,14 @@ method:
Update Queries
~~~~~~~~~~~~~~

Doctrine also supports executing atomic update queries using the `Query\Builder` object. You can use the conditional operations in combination with the ability to change document field values atomically. You have several modifier operations available to you that make it easy to update documents in Mongo:
Doctrine also supports executing atomic update queries using the `Query\Builder`
object. You can use the conditional operations in combination with the ability to
change document field values atomically. Additionally if you are modifying a field
that is a reference you can pass managed document to the Builder and let ODM build
``DBRef`` object for you.

You have several modifier operations
available to you that make it easy to update documents in Mongo:

* ``set($name, $value, $atomic = true)``
* ``setNewObj($newObj)``
Expand Down
Expand Up @@ -189,7 +189,7 @@ private function generateMethod(\ReflectionMethod $method)
/**
* {@inheritDoc}
*/
public function {$method->name}($parametersString)
public function {$method->name}($parametersString){$this->getMethodReturnType($method)}
{
\$this->initialize();
if (\$this->needsSchedulingForDirtyCheck()) {
Expand Down Expand Up @@ -297,4 +297,65 @@ function (\ReflectionParameter $parameter) {
$parameters
);
}

/**
* @param \ReflectionMethod $method
*
* @return string
*
* @see \Doctrine\Common\Proxy\ProxyGenerator::getMethodReturnType()
*/
private function getMethodReturnType(\ReflectionMethod $method)
{
if ( ! method_exists($method, 'hasReturnType') || ! $method->hasReturnType()) {
return '';
}
return ': ' . $this->formatType($method->getReturnType(), $method);
}

/**
* @param \ReflectionType $type
* @param \ReflectionMethod $method
* @param \ReflectionParameter|null $parameter
*
* @return string
*
* @see \Doctrine\Common\Proxy\ProxyGenerator::formatType()
*/
private function formatType(
\ReflectionType $type,
\ReflectionMethod $method,
\ReflectionParameter $parameter = null
) {
$name = method_exists($type, 'getName') ? $type->getName() : (string) $type;
$nameLower = strtolower($name);
if ('self' === $nameLower) {
$name = $method->getDeclaringClass()->getName();
}
if ('parent' === $nameLower) {
$name = $method->getDeclaringClass()->getParentClass()->getName();
}
if ( ! $type->isBuiltin() && ! class_exists($name) && ! interface_exists($name)) {
if (null !== $parameter) {
throw PersistentCollectionException::invalidParameterTypeHint(
$method->getDeclaringClass()->getName(),
$method->getName(),
$parameter->getName()
);
}
throw PersistentCollectionException::invalidReturnTypeHint(
$method->getDeclaringClass()->getName(),
$method->getName()
);
}
if ( ! $type->isBuiltin()) {
$name = '\\' . $name;
}
if ($type->allowsNull()
&& (null === $parameter || ! $parameter->isDefaultValueAvailable() || null !== $parameter->getDefaultValue())
) {
$name = '?' . $name;
}
return $name;
}
}
Expand Up @@ -42,4 +42,50 @@ public static function namespaceRequired()
{
return new self('You must configure a PersistentCollection namespace. See docs for details');
}

/**
* @param string $className
* @param string $methodName
* @param string $parameterName
* @param \Exception|null $previous
*
* @return self
*/
public static function invalidParameterTypeHint(
$className,
$methodName,
$parameterName,
\Exception $previous = null
) {
return new self(
sprintf(
'The type hint of parameter "%s" in method "%s" in class "%s" is invalid.',
$parameterName,
$methodName,
$className
),
0,
$previous
);
}

/**
* @param $className
* @param $methodName
* @param \Exception|null $previous
*
* @return self
*/
public static function invalidReturnTypeHint($className, $methodName, \Exception $previous = null)
{
return new self(
sprintf(
'The return type of method "%s" in class "%s" is invalid.',
$methodName,
$className
),
0,
$previous
);
}
}
22 changes: 14 additions & 8 deletions lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
Expand Up @@ -971,27 +971,28 @@ public function addFilterToPreparedQuery(array $preparedQuery)
* PHP field names and types will be converted to those used by MongoDB.
*
* @param array $query
* @param bool $isNewObj
* @return array
*/
public function prepareQueryOrNewObj(array $query)
public function prepareQueryOrNewObj(array $query, $isNewObj = false)
{
$preparedQuery = array();

foreach ($query as $key => $value) {
// Recursively prepare logical query clauses
if (in_array($key, array('$and', '$or', '$nor')) && is_array($value)) {
foreach ($value as $k2 => $v2) {
$preparedQuery[$key][$k2] = $this->prepareQueryOrNewObj($v2);
$preparedQuery[$key][$k2] = $this->prepareQueryOrNewObj($v2, $isNewObj);
}
continue;
}

if (isset($key[0]) && $key[0] === '$' && is_array($value)) {
$preparedQuery[$key] = $this->prepareQueryOrNewObj($value);
$preparedQuery[$key] = $this->prepareQueryOrNewObj($value, $isNewObj);
continue;
}

$preparedQueryElements = $this->prepareQueryElement($key, $value, null, true);
$preparedQueryElements = $this->prepareQueryElement($key, $value, null, true, $isNewObj);
foreach ($preparedQueryElements as list($preparedKey, $preparedValue)) {
$preparedQuery[$preparedKey] = is_array($preparedValue)
? array_map('\Doctrine\ODM\MongoDB\Types\Type::convertPHPToDatabaseValue', $preparedValue)
Expand All @@ -1011,10 +1012,11 @@ public function prepareQueryOrNewObj(array $query)
* @param string $fieldName
* @param mixed $value
* @param ClassMetadata $class Defaults to $this->class
* @param boolean $prepareValue Whether or not to prepare the value
* @param bool $prepareValue Whether or not to prepare the value
* @param bool $inNewObj Whether or not newObj is being prepared
* @return array An array of tuples containing prepared field names and values
*/
private function prepareQueryElement($fieldName, $value = null, $class = null, $prepareValue = true)
private function prepareQueryElement($fieldName, $value = null, $class = null, $prepareValue = true, $inNewObj = false)
{
$class = isset($class) ? $class : $this->class;

Expand All @@ -1037,7 +1039,7 @@ private function prepareQueryElement($fieldName, $value = null, $class = null, $

if (! empty($mapping['reference']) && is_object($value) && ! ($value instanceof \MongoId)) {
try {
return $this->prepareDbRefElement($fieldName, $value, $mapping);
return $this->prepareDbRefElement($fieldName, $value, $mapping, $inNewObj);
} catch (MappingException $e) {
// do nothing in case passed object is not mapped document
}
Expand Down Expand Up @@ -1400,11 +1402,15 @@ private function getWriteOptions(array $options = array())
* @param string $fieldName
* @param mixed $value
* @param array $mapping
* @param bool $inNewObj
* @return array
*/
private function prepareDbRefElement($fieldName, $value, array $mapping)
private function prepareDbRefElement($fieldName, $value, array $mapping, $inNewObj)
{
$dbRef = $this->dm->createDBRef($value, $mapping);
if ($inNewObj) {
return [[$fieldName, $dbRef]];
}
$keys = ['$ref' => true, '$id' => true, '$db' => true];
if ($mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
unset($keys['$db']);
Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/ODM/MongoDB/Query/Expr.php
Expand Up @@ -160,7 +160,7 @@ public function getNewObj()
{
return $this->dm->getUnitOfWork()
->getDocumentPersister($this->class->name)
->prepareQueryOrNewObj($this->newObj);
->prepareQueryOrNewObj($this->newObj, true);
}

/**
Expand Down
19 changes: 9 additions & 10 deletions lib/Doctrine/ODM/MongoDB/UnitOfWork.php
Expand Up @@ -745,16 +745,6 @@ private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $r
if ($orgValue !== null) {
$this->scheduleOrphanRemoval($orgValue);
}

if ($actualValue !== null) {
list(, $knownParent, ) = $this->getParentAssociation($actualValue);
if ($knownParent && $knownParent !== $document) {
$actualValue = clone $actualValue;
$class->setFieldValue($document, $class->fieldMappings[$propName]['fieldName'], $actualValue);
$this->setOriginalDocumentProperty(spl_object_hash($document), $class->fieldMappings[$propName]['fieldName'], $actualValue);
}
}

$changeSet[$propName] = array($orgValue, $actualValue);
continue;
}
Expand Down Expand Up @@ -998,6 +988,10 @@ private function computeAssociationChanges($parentDocument, array $assoc, $value
if ($assoc['type'] === ClassMetadata::ONE) {
$class->setFieldValue($parentDocument, $assoc['fieldName'], $entry);
$this->setOriginalDocumentProperty(spl_object_hash($parentDocument), $assoc['fieldName'], $entry);
$poid = spl_object_hash($parentDocument);
if (isset($this->documentChangeSets[$poid][$assoc['fieldName']])) {
$this->documentChangeSets[$poid][$assoc['fieldName']][1] = $entry;
}
} else {
// must use unwrapped value to not trigger orphan removal
$unwrappedValue[$key] = $entry;
Expand Down Expand Up @@ -2193,6 +2187,11 @@ function ($assoc) { return $assoc['isCascadePersist']; }
}
} elseif ($relatedDocuments !== null) {
if ( ! empty($mapping['embedded'])) {
list(, $knownParent, ) = $this->getParentAssociation($relatedDocuments);
if ($knownParent && $knownParent !== $document) {
$relatedDocuments = clone $relatedDocuments;
$class->setFieldValue($document, $mapping['fieldName'], $relatedDocuments);
}
$this->setParentAssociation($relatedDocuments, $mapping, $document, $mapping['fieldName']);
}
$this->doPersist($relatedDocuments, $visited);
Expand Down
Expand Up @@ -7,7 +7,7 @@

class GH1525Test extends \Doctrine\ODM\MongoDB\Tests\BaseTest
{
public function testEmbedClone()
public function testEmbedCloneTwoFlushesPerDocument()
{
$embedded = new GH1525Embedded('embedded');

Expand Down Expand Up @@ -38,6 +38,56 @@ public function testEmbedClone()
$this->assertSame($test->embedMany[0]->name, $embedMany->name);
}
}

public function testEmbedCloneWithIdStrategyNoneOnParentAndEarlyPersist()
{
$uuidGen = new \Doctrine\ODM\MongoDB\Id\UuidGenerator();
$embedded = new GH1525Embedded('embedded');

$count = 2;
for ($i = 0; $i < $count; ++$i) {
$parent = new GH1525DocumentIdStrategyNone($uuidGen->generateV4(), 'test' . $i);
$this->dm->persist($parent);
$parent->embedded = $embedded;
$this->dm->flush();
}

$this->dm->clear();

for ($i = 0; $i < $count; ++$i) {
$test = $this->dm->getRepository(GH1525DocumentIdStrategyNone::class)->findOneBy(array('name' => 'test' . $i));

$this->assertInstanceOf(GH1525DocumentIdStrategyNone::class, $test);

$this->assertInstanceOf(GH1525Embedded::class, $test->embedded);
$this->assertSame($test->embedded->name, $embedded->name);
}
}

public function testEmbedCloneWithIdStrategyNoneOnParentAndLatePersist()
{
$uuidGen = new \Doctrine\ODM\MongoDB\Id\UuidGenerator();
$embedded = new GH1525Embedded('embedded');

$count = 2;
for ($i = 0; $i < $count; ++$i) {
$parent = new GH1525DocumentIdStrategyNone($uuidGen->generateV4(), 'test' . $i);
$parent->embedded = $embedded;
$this->dm->persist($parent);
$this->dm->flush();
}

$this->dm->clear();

for ($i = 0; $i < $count; ++$i) {
$test = $this->dm->getRepository(GH1525DocumentIdStrategyNone::class)->findOneBy(array('name' => 'test' . $i));

$this->assertInstanceOf(GH1525DocumentIdStrategyNone::class, $test);

$this->assertInstanceOf(GH1525Embedded::class, $test->embedded);
$this->assertSame($test->embedded->name, $embedded->name);
}
}
}

/** @ODM\Document(collection="document_test") */
Expand All @@ -62,6 +112,24 @@ public function __construct($name)
}
}

/** @ODM\Document(collection="document_test_with_auto_ids") */
class GH1525DocumentIdStrategyNone
{
/** @ODM\Id(strategy="NONE") */
public $id;

/** @ODM\Field(type="string") */
public $name;

/** @ODM\EmbedOne(targetDocument="GH1525Embedded") */
public $embedded;

public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
}

/** @ODM\EmbeddedDocument */
class GH1525Embedded
Expand Down
@@ -0,0 +1,10 @@
<?php

namespace Doctrine\ODM\MongoDB\Tests\PersistentCollection;

use Doctrine\Common\Collections\ArrayCollection;

class CollNoReturnType extends ArrayCollection
{

}

0 comments on commit 1386959

Please sign in to comment.