-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Introduce the Revisionable extension #2825
base: main
Are you sure you want to change the base?
Conversation
758fae8
to
9428ca6
Compare
Having no username set in the |
Thanks, I've updated everything to account for that. |
25c3e45
to
6f21faf
Compare
b143ad5
to
f4bc83a
Compare
761f013
to
8af0d92
Compare
What's the status of this PR? Anything I can do to help/test? Would like to see it merged soon! |
I don't think I have anything big to add to this PR at this point. It really just needs testing and making sure the idea is solid (especially as part of the rationale for a new extension versus trying to make a version of loggable that is DBAL 4 friendly is allowing both the old and new data to live side-by-side so an app can either keep that old data by either polyfilling the deprecated DBAL array type or migrating it into the new extension). |
99c5d53
to
5b3b1cb
Compare
I started trying to test/use this in an ODM configuration. Notables so far:
So at the moment I've not been able to create/trigger any revisions, I guess there is a way to enable this without EDIT: Yes, the
|
The loggable listener has to be open because of its Maybe it's too soon to go with a hard final listener, but I'm not going to take the "no I'm not going to open it up for inheritance" standpoint if there's a legitimate use case that a hard final blocks. Maybe somewhere down the road, something can be figured out for how interfaces can be built for the listeners (as right now the hook points into Doctrine are all Doctrine event listeners, so the interface is more so specifying the required events to subscribe to and less what a public-ish API looks like).
The new extension is more strictly typed given it's being written as new code with the PHP 7.4 minimum in mind, compared to all of the other extensions which were written in PHP 5 times;
I wanted to make the revision model a little more strict in terms of having valid state, hence the introduction of the static
That'd have to be done after this PR landed. Trying to shoot a PR off over there would just result in a PR with constantly failing builds, it's easier to handle that update after this change lands. For manual config in a Symfony app, you can take the loggable listener service in https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/frameworks/symfony.md#extensions-compatible-with-all-managers and change that to the revisionable listener (it'll hook into the same events so it's just changing the service ID and class name)
It'll get done before this merges. I did call out the needed mapping changes in the PR description, but the gist of it is you switch |
If action is no longer nullable does it make sense set a default then, to avoid e.g.
(or whatever, if the syntax is slightly off) |
Probably; this has all been iterated over a few times so little pain points like that are bound to have slipped in by accident. |
59c1546
to
366f9fd
Compare
This has been updated. |
For reference this is what I'm using for now.
Then to get |
Next problem, there seems to be a circular issue with ODM embedded documents.
In fairness the same problem exists with loggable, I guess it worked at some point, then got broken. I would suggest that embedded documents should require versioned attributes against fields, but not revisionable/loggable attributes against the class. In testing this works. Looks like this was previsouly enabled for ORM (isEmbeddedClass?), but the one that fixes it for me in ODM is isEmbeddedDocument. e.g.
src/Loggable/Mapping/Driver/Attribute.php
|
Do you have an example you can share where it's not working? I'm not a ODM user, but I went through the effort to get MongoDB running in my setup for a couple OSS projects so I was able to build out the test fixtures for this new code, which include embedded documents. One of the differences between the ODM and ORM is how they track embedded objects. The ORM inlines embeds as part of the entity itself, while the ODM tracks them as managed objects in the unit of work. An earlier iteration tried to not use the Revisionable attribute on embedded documents, but because of that difference between the two object managers, the embedded document wasn't being handled in the event listener. |
Not sure what you're after exactly, but here's a slightly stripped back example of what I'm testing with, from a Symfony project. App/Document/Product.php<?php
namespace App\Document;
use App\Document\Log;
use App\Document\Price;
use App\Document\Revision;
use App\Repository\ProductRepository;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Gedmo\Mapping\Annotation as Gedmo;
use Money\Money;
#[MongoDB\Document(collection: 'products', repositoryClass: ProductRepository::class)]
#[Gedmo\Loggable(logEntryClass: Log::class)]
#[Gedmo\Revisionable(revisionClass: Revision::class)]
class Product
{
#[MongoDB\Id(strategy: 'UUID')]
private ?string $identifier = null;
#[MongoDB\EmbedOne(targetDocument: Price::class)]
#[Gedmo\Versioned]
private ?Price $price = null;
public function getId(): ?string
{
return $this->identifier;
}
public function getPrice(): ?Money
{
if (isset($this->price)) {
return $this->price->getPrice();
}
return null;
}
public function setPrice(?Money $price): static
{
$this->price = null;
if ($price instanceof Money) {
$this->price = new Price($price);
}
return $this;
}
} App/Document/Price.php<?php
namespace App\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Gedmo\Mapping\Annotation as Gedmo;
use Money\Currency;
use Money\Money;
#[MongoDB\EmbeddedDocument]
class Price
{
#[MongoDB\Id(strategy: 'UUID')]
private ?string $identifier = null;
#[MongoDB\Field(type: 'decimal128')]
#[Gedmo\Versioned]
private ?string $amount = null;
#[MongoDB\Field(type: 'string')]
#[Gedmo\Versioned]
private ?string $currency = null;
public function __construct(Money $price)
{
$this->setPrice($price);
}
public function getId(): ?string
{
return $this->identifier;
}
public function getAmount(): ?string
{
return $this->amount;
}
public function setAmount(string $amount): static
{
$this->amount = $amount;
return $this;
}
public function getCurrency(): ?string
{
return $this->currency;
}
public function setCurrency(string $currency): static
{
$this->currency = $currency;
return $this;
}
public function getPrice(): ?Money
{
if (isset($this->amount) && is_numeric($this->amount) && isset($this->currency) && $this->currency != '') {
$decimals = 2;
$amount = (string) ((float) $this->getAmount() * 10 ** $decimals);
return new Money($amount, new Currency($this->currency));
}
return null;
}
public function setPrice(Money $price): static
{
$decimals = 2;
$amount = (string) ((float) $price->getAmount() / 10 ** $decimals);
$this->amount = $amount;
$this->currency = $price->getCurrency()->getCode();
return $this;
}
} It works with the above changes and revision data gets set to:
Without, it gets set to:
Similarly with the log data. The difference I notice is in the database they're all strings for loggable, whereas for revisionable they are the correct field type. |
I'll play with that a bit and see what I can come up with.
That's the other big change in this new extension. With loggable, it would just take the values from the objects and serialize the array. In the new code, I'm going through the |
591d002
to
561a3ae
Compare
OK, I think I have this back to not requiring the revisionable attribute (or similar config in XML/YAML) on embedded models. The fields that should be versioned still need to be configured as such, but that looks to be the same with the current loggable extension so this should be OK. |
Fantastic, that appears to work perfectly... for revisionable. Which in theory should be fine, except I think the situation for embeds is:
As an embedded document with versioned attributes gives the error:
I get that it's probably kind of out of scope to add features to loggable in this PR, but as it's reusing the versioned attribute, and loggable and revisionable probably need to exist in tandem for a period of time, what would you suggest as the best resolution? My one line change to On second thought, what you're probably expecting to happen is that the two exist in tandem in code only, and aren't actually used together. Meaning ODM embed's aren't supported by loggable (and probably never were), but are supported by revisionable, so you'd have to migrate, disabling loggable in the process. |
In my mind, the two extensions can live side-by-side (which also gives a way to migrate data across if you need to, like the hacked together gist in the OP shows can be done), but a model shouldn't be both loggable and revisionable at the same time. That does get trickier with embedded models because of the behavioral differences between object managers, but if tweaking the loggable extension makes the migration more practical, then we should do that as well. As a separate PR (because it sounds like the limitations with loggable and the ODM are already there and addressing that shouldn't rely on this PR landing anytime soon), I'd say to make the changes you're suggesting in the mapping drivers for the loggable extension, take the |
561a3ae
to
d0c2471
Compare
@mbabker Hey 👋 Great work on this new extension! Do you have an indication of this PR's status, or an idea when it could get merged? In my project we'll be needing something like this soon. And because I hope you don't mind me asking this directly 😊 I've sent a contribution through GitHub Sponsors for your work on this! |
Thank you!!!
I've gone ahead and marked the PR ready for review. We've been sitting on this for a bit, and it's gotten some feedback in passing, which has helped get it into a pretty good state overall. The one rough edge is probably the cases dealing with embedded models from a few comments back for folks who do need to migrate and have their apps support both loggable and revisionable for different models (i.e. an embedded So I think we just need some good real-world testing on this PR and we can look at shipping it. |
Ref: #2502
As the issue notes, the loggable extension is incompatible with ORM 4.x due to the removal of the array field type. The issue also has a patch for migrating to JSON, but because of the need for a data migration, it's not a patch that can be easily dropped in. Enter the new revisionable extension.
Mostly the same thing as the loggable extension, except for changing the way the data is collected for the history object's data field. For the ORM, the data was stored as a serialized PHP array, which while it works, is less than optimal:
The new extension uses the mapping layers of the ORM and ODM to store the data in its database representation, and for the ORM, as a native JSON field:
Another benefit to introducing a new extension is that the old and new versions can live side-by-side and allows for a data migration so long as your log entry data can be unserialized back into PHP and its info mapped to a database value (https://gist.github.com/mbabker/1879a3e55feac953e23cb2f025654052 was something I quickly hacked into the example app code as a proof of concept, a full-on tool could be built out using the logic here).
There are some other extras baked into this branch which generally help improve things too, including:
WrapperInterface
implementations (and@method
annotated on the interface itself to add to 4.0) to handle mapping values to PHP and database formats for each managerDifferences between the two extensions include:
prePersistLogEntry()
hook that existed in the loggable listener, the Doctrine event manager can be used here without needing to have an open classRevisionable
attribute instead ofLoggable
, and reuses the existingVersioned
attribute; for migrating, you're basically looking at a handful of lines changed no matter the mapping (changing a class import and annotation/attribute when using that mapping, XML goes from<gedmo:loggable/>
to<gedmo:revisionable/>