Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for versioning of @EmbedMany ODM relations #2791

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions src/Loggable/Document/Repository/LogEntryRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Gedmo\Loggable\Document\Repository;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
use Gedmo\Exception\RuntimeException;
Expand Down Expand Up @@ -41,15 +42,17 @@ class LogEntryRepository extends DocumentRepository
* Loads all log entries for the
* given $document
*
* @param object $document
* @param object $document
* @param int|null $limit
* @param int|null $skip
*
* @return LogEntry[]
*
* @phpstan-param T $document
*
* @phpstan-return array<array-key, LogEntry<T>>
*/
public function getLogEntries($document)
public function getLogEntries($document, $limit = null, $skip = null)
{
$wrapped = new MongoDocumentWrapper($document, $this->dm);
$objectId = $wrapped->getIdentifier();
Expand All @@ -58,6 +61,12 @@ public function getLogEntries($document)
$qb->field('objectId')->equals($objectId);
$qb->field('objectClass')->equals($wrapped->getMetadata()->getName());
$qb->sort('version', 'DESC');
if ($limit) {
$qb->limit($limit);
}
if ($skip) {
$qb->skip($skip);
}

return $qb->getQuery()->getIterator()->toArray();
}
Expand Down Expand Up @@ -97,7 +106,15 @@ public function revert($document, $version = 1)

$data = [[]];
while ($log = array_shift($logs)) {
$data[] = $log->getData();
$logData = $log->getData();
foreach ($logData as $field => $value) {
if ($value && $wrapped->isEmbeddedCollectionAssociation($field)) {
foreach ($value as $i => $item) {
$logData[$field][$i] = array_merge(@$data[$field][$i] ?: [], $item);
}
}
}
$data[] = $logData;
}
$data = array_merge(...$data);
$this->fillDocument($document, $data);
Expand Down Expand Up @@ -128,15 +145,16 @@ protected function fillDocument($document, array $data)
}
$mapping = $objectMeta->getFieldMapping($field);
// Fill the embedded document
if ($wrapped->isEmbeddedAssociation($field)) {
if ($wrapped->isEmbeddedCollectionAssociation($field)) {
if (!empty($value)) {
assert(class_exists($mapping['targetDocument']));

$embeddedMetadata = $this->dm->getClassMetadata($mapping['targetDocument']);
$document = $embeddedMetadata->newInstance();
$this->fillDocument($document, $value);
$value = $document;
$items = [];
foreach ($value as $item) {
$items[] = $this->fillEmbeddedDocument($item, $mapping);
}
$value = new ArrayCollection($items);
}
} elseif ($wrapped->isEmbeddedAssociation($field)) {
$value = $this->fillEmbeddedDocument($value, $mapping);
} elseif ($objectMeta->isSingleValuedAssociation($field)) {
assert(class_exists($mapping['targetDocument']));

Expand All @@ -153,6 +171,27 @@ protected function fillDocument($document, array $data)
*/
}

/**
* @param array<string, mixed> $value
* @param array<string, mixed> $mapping
*
* @return ?object
*/
protected function fillEmbeddedDocument($value, $mapping)
{
if (!empty($value)) {
assert(class_exists($mapping['targetDocument']));

$embeddedMetadata = $this->dm->getClassMetadata($mapping['targetDocument']);
$document = $embeddedMetadata->newInstance();
$this->fillDocument($document, $value);

return $document;
}

return null;
}

/**
* Get the currently used LoggableListener
*
Expand Down
8 changes: 8 additions & 0 deletions src/Loggable/LoggableListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ protected function getObjectChangeSetData($ea, $object, $logEntry)
continue;
}
$value = $changes[1];
if (method_exists($meta, 'isCollectionValuedEmbed') && $meta->isCollectionValuedEmbed($field) && $value) {
$embedValues = [];
foreach ($value as $embedValue) {
$wrapped = AbstractWrapper::wrap($embedValue, $om);
$embedValues[] = $this->getObjectChangeSetData($ea, $embedValue, $logEntry);
}
$value = $embedValues;
}
if ($meta->isSingleValuedAssociation($field) && $value) {
if ($wrapped->isEmbeddedAssociation($field)) {
$value = $this->getObjectChangeSetData($ea, $value, $logEntry);
Expand Down
7 changes: 6 additions & 1 deletion src/Loggable/Mapping/Driver/Annotation.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ public function readExtendedMetadata($meta, array &$config)

// versioned property
if ($this->reader->getPropertyAnnotation($property, self::VERSIONED)) {
if (!$this->isMappingValid($meta, $field)) {
$isEmbedMany = false;
if (method_exists($meta, 'isCollectionValuedEmbed')) {
$isEmbedMany = $meta->isCollectionValuedEmbed($field);
}

if ($meta->isCollectionValuedAssociation($field) && !$isEmbedMany) {
throw new InvalidMappingException("Cannot apply versioning to field [{$field}] as it is collection in object - {$meta->getName()}");
}
if (isset($meta->embeddedClasses[$field])) {
Expand Down
5 changes: 5 additions & 0 deletions src/Tool/Wrapper/EntityWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ public function isEmbeddedAssociation($field)
return false;
}

public function isEmbeddedCollectionAssociation($field)
{
return false;
}

/**
* Initialize the entity if it is proxy
* required when is detached or not initialized
Expand Down
5 changes: 5 additions & 0 deletions src/Tool/Wrapper/MongoDocumentWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ public function isEmbeddedAssociation($field)
return $this->getMetadata()->isSingleValuedEmbed($field);
}

public function isEmbeddedCollectionAssociation($field)
{
return $this->getMetadata()->isCollectionValuedEmbed($field);
}

/**
* Initialize the document if it is proxy
* required when is detached or not initialized
Expand Down
9 changes: 9 additions & 0 deletions src/Tool/WrapperInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,13 @@ public function getRootObjectName();
* @return bool
*/
public function isEmbeddedAssociation($field);

/**
* Checks if association is a collection of embedded objects.
*
* @param string $field
*
* @return bool
*/
public function isEmbeddedCollectionAssociation($field);
}
33 changes: 33 additions & 0 deletions tests/Gedmo/Loggable/Fixture/Document/Article.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Gedmo\Tests\Loggable\Fixture\Document;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Types\Type;
use Gedmo\Loggable\Loggable;
Expand Down Expand Up @@ -51,6 +52,22 @@ class Article implements Loggable
#[Gedmo\Versioned]
private ?Author $author = null;

/**
* @var ?ArrayCollection<array-key, Reference>
*
* @ODM\EmbedMany(targetDocument="Gedmo\Tests\Loggable\Fixture\Document\Reference")
*
* @Gedmo\Versioned
*/
#[ODM\EmbedMany(targetDocument: Reference::class)]
#[Gedmo\Versioned]
private ?ArrayCollection $references;

public function __construct()
{
$this->references = new ArrayCollection();
}

public function __toString()
{
return $this->title;
Expand Down Expand Up @@ -80,4 +97,20 @@ public function getAuthor(): ?Author
{
return $this->author;
}

/**
* @param ?ArrayCollection<array-key, Reference> $references
*/
public function setReferences(?ArrayCollection $references): void
{
$this->references = $references;
}

/**
* @return ?ArrayCollection<array-key, Reference>
*/
public function getReferences(): ?ArrayCollection
{
return $this->references;
}
}
65 changes: 65 additions & 0 deletions tests/Gedmo/Loggable/Fixture/Document/Reference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Doctrine Behavioral Extensions package.
* (c) Gediminas Morkevicius <gediminas.morkevicius@gmail.com> http://www.gediminasm.org
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gedmo\Tests\Loggable\Fixture\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Types\Type;
use Gedmo\Loggable\Loggable;
use Gedmo\Mapping\Annotation as Gedmo;

/**
* @ODM\EmbeddedDocument
*
* @Gedmo\Loggable
*/
#[ODM\EmbeddedDocument]
#[Gedmo\Loggable]
class Reference implements Loggable
{
/**
* @Gedmo\Versioned
*
* @ODM\Field(type="string")
*/
#[ODM\Field(type: Type::STRING)]
#[Gedmo\Versioned]
private ?string $reference = null;

/**
* @Gedmo\Versioned
*
* @ODM\Field(type="string")
*/
#[ODM\Field(type: Type::STRING)]
#[Gedmo\Versioned]
private ?string $title = null;

public function setReference(?string $reference): void
{
$this->reference = $reference;
}

public function getReference(): ?string
{
return $this->reference;
}

public function setTitle(?string $title): void
{
$this->title = $title;
}

public function getTitle(): ?string
{
return $this->title;
}
}
61 changes: 44 additions & 17 deletions tests/Gedmo/Loggable/Fixture/Document/RelatedArticle.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
namespace Gedmo\Tests\Loggable\Fixture\Document;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\Types\Type;
use Gedmo\Loggable\Loggable;
Expand Down Expand Up @@ -54,37 +53,35 @@ class RelatedArticle implements Loggable
private ?string $content = null;

/**
* @var Collection<int, Comment>
* @var ?ArrayCollection<array-key, Comment>
*
* @ODM\ReferenceMany(targetDocument="Gedmo\Tests\Loggable\Fixture\Document\Comment", mappedBy="article")
*/
#[ODM\ReferenceMany(targetDocument: Comment::class, mappedBy: 'article')]
private $comments;
private ?ArrayCollection $comments = null;

/**
* @var ?ArrayCollection<array-key, Reference>
*
* @ODM\EmbedMany(targetDocument="Gedmo\Tests\Loggable\Fixture\Document\Reference")
*
* @Gedmo\Versioned
*/
#[ODM\EmbedMany(targetDocument: Reference::class)]
#[Gedmo\Versioned]
private ?ArrayCollection $references = null;

public function __construct()
{
$this->comments = new ArrayCollection();
$this->references = new ArrayCollection();
}

public function getId(): ?string
{
return $this->id;
}

public function addComment(Comment $comment): void
{
$comment->setArticle($this);
$this->comments[] = $comment;
}

/**
* @return Collection<int, Comment>
*/
public function getComments(): Collection
{
return $this->comments;
}

public function setTitle(?string $title): void
{
$this->title = $title;
Expand All @@ -104,4 +101,34 @@ public function getContent(): ?string
{
return $this->content;
}

public function addComment(Comment $comment): void
{
$comment->setArticle($this);
$this->comments[] = $comment;
}

/**
* @return ?ArrayCollection<array-key, Comment>
*/
public function getComments(): ?ArrayCollection
{
return $this->comments;
}

/**
* @param ?ArrayCollection<array-key, Reference> $references
*/
public function setReferences(?ArrayCollection $references): void
{
$this->references = $references;
}

/**
* @return ?ArrayCollection<array-key, Reference>
*/
public function getReferences(): ?ArrayCollection
{
return $this->references;
}
}
Loading