Closed mansoorkhan96 closed 8 months ago
I am working on this.
Right now, changing the logic for VersionStrategy::SNAPSHOT
might introduce a breaking change.
I am working on this.
Great.
Thanks!
What do you think the following definitions:
$versionable
$versionable
, not all of the model attributes.so maybe we should add a strategy named FULL_SNAPSHOT
? or any other name?
What do you think the following definitions:
- DIFF: Changed attributes in the
$versionable
- SNAPSHOT: All the attributes in the
$versionable
, not all of the model attributes.
Thats the ideal definition.
But.. Would not it introduce a breaking change? Users might be using SNAPSHOT to save all the Model attributes and now when they upgrade they would only see the $versionable attributes.
so maybe we should add a strategy named
FULL_SNAPSHOT
? or any other name?
FULL_SNAPSHOT is nice
What do you think the following definitions:
- DIFF: Changed attributes in the
$versionable
- SNAPSHOT: All the attributes in the
$versionable
, not all of the model attributes.Thats the ideal definition.
But.. Would not it introduce a breaking change? Users might be using SNAPSHOT to save all the Model attributes and now when they upgrade they would only see the $versionable attributes.
Yes, we can create a major version.
What do you think the following definitions:
- DIFF: Changed attributes in the
$versionable
- SNAPSHOT: All the attributes in the
$versionable
, not all of the model attributes.Thats the ideal definition. But.. Would not it introduce a breaking change? Users might be using SNAPSHOT to save all the Model attributes and now when they upgrade they would only see the $versionable attributes.
Yes, we can create a major version.
Great, we are good then.
Now that you are releasing a major version. Maybe refactor VersionStrategy class to an enum?
What do you think the following definitions:
- DIFF: Changed attributes in the
$versionable
- SNAPSHOT: All the attributes in the
$versionable
, not all of the model attributes.Thats the ideal definition. But.. Would not it introduce a breaking change? Users might be using SNAPSHOT to save all the Model attributes and now when they upgrade they would only see the $versionable attributes.
Yes, we can create a major version.
Great, we are good then.
- DIFF: Changed attributes in the $versionable
- SNAPSHOT: All the attributes in the $versionable, not all of the model attributes.
- FULL_SNAPSHOT or FULL or COMPLETE or ALL: All of the model attributes.
Now that you are releasing a major version. Maybe refactor VersionStrategy class to an enum?
Great idea!
Here's a query, I don't know what you think, I think that the DIFF model probably shouldn't exist, for example:
// model Post protected $versionable = ['title', 'content', 'extends'];
$post = Post::create(['title' => 'version1', 'content' => 'version1 content']); $post->update(['title' => 'version2', 'extends' => ['foo' => 'bar' ]]); $post->versions.
$post->versions:
//[id => 1, 'title' => 'version1', 'content' => 'version1 content']
//[id => 2, 'title' => 'version2', extends => ['foo' => 'bar'] ] ```
If we roll back to version 1, we will get unexpected results:
```php
$post->firstVersion->revert(); dd($post->extends); // ['foo' => 'bar']
Because the implementation logic is taking the version 1 contents (title, content)
and overriding the current field (title,content)
, the extends
are not overridden, leading to the wrong result, and since extends
is in the versionable, it looks like a bug.
Here's a query, I don't know what you think, I think that the DIFF model probably shouldn't exist, for example:
// model Post protected $versionable = ['title', 'content', 'extends']; $post = Post::create(['title' => 'version1', 'content' => 'version1 content']); $post->update(['title' => 'version2', 'extends' => ['foo' => 'bar' ]]); $post->versions. $post->versions: //[id => 1, 'title' => 'version1', 'content' => 'version1 content'] //[id => 2, 'title' => 'version2', extends => ['foo' => 'bar'] ] ``` If we roll back to version 1, we will get unexpected results: ```php $post->firstVersion->revert(); dd($post->extends); // ['foo' => 'bar']
Because the implementation logic is taking the version 1
contents (title, content)
and overriding the current field(title,content)
, theextends
are not overridden, leading to the wrong result, and sinceextends
is in the versionable, it looks like a bug.
Let me try that.
@overtrue I think you are right. Reverting with DIFF is complex, because you dont have the attributes and you dont know what was the state for the unchanged attributes in that version.
yes, so i think we will remove this strategy. 🤣
I am working on branch 5.x
Let's do some work first and keep changing it later, feel free to discuss any comments, thanks!
Let's do some work first and keep changing it later, feel free to discuss any comments, thanks!
Great. Let me know if you want to assign anything to me.
For now I'm hoping that when I'm done with the changes you can help verify that all of your needs are met and that your package is working properly, thanks!
For now I'm hoping that when I'm done with the changes you can help verify that all of your needs are met and that your package is working properly, thanks!
No problems, I will do that.
Hi, I've found a problem, if you are in diff mode, the revert version should be stacked with all the versions from the initial version, while snapshot will just use that version.
revert to version 3
DIFF: array_merge(version 1, version 2, version 3)
so we must keep the first version with full mode:
if ($post->isFirstVersion()) {
$version->contents = $post->refresh()->getAttributes();
} else {
$version->contents = by strategy.
}
These three schemas have the following characteristics:
DIFF 1.1. the initialized version is for all fields and is required to get the default values of the fields from the database; 1.2. the version history keeps only every change; 1.3. rollbacks are stacked chronologically from the initialization version to the target version; 1.4. deleting a version will affect rollback behavior.
SNAPSHOT 2.1. the initialized version is for all fields and is required to get the default values of the fields from the database; 2.2. the version history keeps only the fields that were changed in each $versionables; 2.3. the initial version + the target version is sufficient for rollback; 2.4. deleting the version will not affect the rollback behavior.
FULL/ALL 3.1. the initialized version is for all fields and is required to get the default values of the fields from the database; 3.2. the version history keeps all fields; 3.3. the target version can be used directly for rollback; 3.4. deleting the version will not affect the rollback behavior.
In this way, SNAPSHOT mode can actually be a new version of FULL mode, keeping the original DIFF and SNAPSHOT, and just changing the implementation to the FULL effect. The catch here is that the first version must be the full version.
These three schemas have the following characteristics:
- DIFF 1.1. the initialized version is for all fields and is required to get the default values of the fields from the database; 1.2. the version history keeps only every change; 1.3. rollbacks are stacked chronologically from the initialization version to the target version; 1.4. deleting a version will affect rollback behavior.
- SNAPSHOT 2.1. the initialized version is for all fields and is required to get the default values of the fields from the database; 2.2. the version history keeps only the fields that were changed in each $versionables; 2.3. the initial version + the target version is sufficient for rollback; 2.4. deleting the version will not affect the rollback behavior.
- FULL/ALL 3.1. the initialized version is for all fields and is required to get the default values of the fields from the database; 3.2. the version history keeps all fields; 3.3. the target version can be used directly for rollback; 3.4. deleting the version will not affect the rollback behavior.
I dont understand the problem you mentioned above.
But 2.2 could be following? 2.2. the version history keeps all the $versionables fields; if any of the $versionables fields has changed.
yes.
I mean, my original implementation of "revert" was wrong, my original implementation was:
$post->revertTo($targetVersion);
contents
$post->all()
and $targetVersion->contents
the step 2 is wrong, it is latest attributes
+ $targetVersion->contents
.
it should be first full attributes
+ ($versions)... + $targetVersion->contents
.
do you understand?
I mean, my original implementation of "revert" was wrong, my original implementation was:
$post->revertTo($targetVersion);
- Fetch $targetVersion
contents
- Merge
$post->all()
and$targetVersion->contents
the step 2 is wrong, it is
latest attributes
+$targetVersion->contents
.it should be
first full attributes
+ ($versions)... +$targetVersion->contents
.do you understand?
it should be first full attributes + ($versions)... + $targetVersion->contents.
Well this might be necessary for the DIFF mode, because we only have certain fields in some versions.
But for snapshot, we will have all the fields we care about and can revert easily to any target version we need.
For my usecase, i might eventually write my own revert, as i would allow the user to revert only selected fields.
So, I think the new version will have two strategy like this:
example:
$post = Post::create(['title' => 'Init title', 'type' => 'Blog', 'status' => 'published'])
$post->update(['title' => 'version 2 title']);
$post->update(['status' => 'draft']); // no version
$post->update(['type' => 'Vlog']);
// versions:
// ['id' => 1, 'title' => 'Init title', 'type' => 'Blog', 'status' => 'published']
// ['id' => 2, 'title' => 'version 2 title']
// ['id' => 3, 'type' => 'vlog']
$post->revert(2);
// [
// 'title' => 'version 2 title',
// 'type' => 'Blog',
// 'status' => 'draft' // latest value, because this field is not in $versionable
// ]
So, I think the new version will have two strategy like this:
- DIFF
- init with all attributes(refreshed from database)
- save changed attributes(subset of $versionable)
- rollback: init version + all of versions to $targetVersion + attributes not in the $versionable of latest version.
example:
$post = Post::create(['title' => 'Init title', 'type' => 'Blog', 'status' => 'published']) $post->update(['title' => 'version 2 title']); $post->update(['status' => 'draft']); // no version $post->update(['type' => 'Vlog']); // versions: // ['id' => 1, 'title' => 'Init title', 'type' => 'Blog', 'status' => 'published'] // ['id' => 2, 'title' => 'version 2 title'] // ['id' => 3, 'type' => 'vlog'] $post->revert(2); // [ // 'title' => 'version 2 title', // 'type' => 'Blog', // 'status' => 'draft' // latest value, because this field is not in $versionable // ]
- SNAPSHOT
- init with all attributes(refreshed from database)
- save all attributes
- rollback: $targetVersion
I trust your instincts. Do it if its the best way to get around it ❤️
init with all attributes(refreshed from database)
But maybe lets make the saving all attributes functionality optional. Because then the diff could get messy.
@mansoorkhan96 Yes, the default strategy is DIFF, so do you think it's better to snapshot $versionable rather than snapshot all attributes?
@mansoorkhan96 Yes, the default strategy is DIFF, so do you think it's better to snapshot $versionable rather than snapshot all attributes?
I think its better to keep $versionable attributes only, when user explicitly opts for that.
like this?
// SNAPSHOT: $versionable = ['title', 'type']
$post = Post::create(['title' => 'Init title', 'type' => 'Blog', 'status' => 'published'])
$post->update(['title' => 'version 2 title']);
$post->update(['status' => 'draft']); // no version
$post->update(['type' => 'Vlog']);
// versions:
// ['id' => 1, 'title' => 'Init title', 'type' => 'Blog', 'status' => 'published']
// ['id' => 2, 'title' => 'version 2 title', 'type' => 'Blog',]
// ['id' => 3, 'title' => 'version 2 title', 'type' => 'vlog']
$post->revert(2);
// [
// 'title' => 'version 2 title',
// 'type' => 'Blog',
// 'status' => 'draft' // latest value, because this field is not in $versionable
// ]
OK, if the $versionable is empty, snapshot all?
like this?
// SNAPSHOT: $versionable = ['title', 'type'] $post = Post::create(['title' => 'Init title', 'type' => 'Blog', 'status' => 'published']) $post->update(['title' => 'version 2 title']); $post->update(['status' => 'draft']); // no version $post->update(['type' => 'Vlog']); // versions: // ['id' => 1, 'title' => 'Init title', 'type' => 'Blog', 'status' => 'published'] // ['id' => 2, 'title' => 'version 2 title', 'type' => 'Blog',] // ['id' => 3, 'title' => 'version 2 title', 'type' => 'vlog'] $post->revert(2); // [ // 'title' => 'version 2 title', // 'type' => 'Blog', // 'status' => 'draft' // latest value, because this field is not in $versionable // ]
Yep, this looks good.
OK, if the $versionable is empty, snapshot all?
yes, that should be the default.
OK, if the $versionable is empty, snapshot all?
yes, that should be the default.
Nice! thank you!
@overtrue FYI, I was inspired by the Wordpress Revisions system. I borrowed some styling from there too.
Maybe you should also try it to see how they handle it. With 0 plugins, when you have updated a post/page 2-3 times. You should see the Revisions button on right side bar and then it opens up the diff page. And it only shows you the diff for the title and content. Its more like what we are doing with snapshot.
@overtrue FYI, I was inspired by the Wordpress Revisions system. I borrowed some styling from there too.
Maybe you should also try it to see how they handle it. With 0 plugins, when you have updated a post/page 2-3 times. You should see the Revisions button on right side bar and then it opens up the diff page. And it only shows you the diff for the title and content. Its more like what we are doing with snapshot.
OK, thanks.
Also, can we pass an a third option to diff functions like toSideBySideHtml
to also include attributes that were not changed.
Right now the diff removes the title if it was not changed.
Thanks.
Also, can we pass an a third option to diff functions like
toSideBySideHtml
to also include attributes that were not changed.Right now the diff removes the title if it was not changed.
Thanks.
Good idea, I will try it after these works.
Also, can we pass an a third option to diff functions like
toSideBySideHtml
to also include attributes that were not changed. Right now the diff removes the title if it was not changed. Thanks.Good idea, I will try it after these works.
You are the man. Thank you so much.
Do you think the initial version should include all attributes?
Do you think the initial version should include all attributes?
I prefer to Yes.
Do you think the initial version should include all attributes?
I prefer to Yes.
Well if needed. But then i have to strip them out and only show the $versionable.
Do you think the initial version should include all attributes?
I prefer to Yes.
Well if needed. But then i have to strip them out and only show the $versionable.
OK.
I have hacked your Diff => render method by manually creating a simple diff for identical strings 😈
public function render(string $renderer = null, array $differOptions = [], array $renderOptions = []): array
{
if (empty($differOptions)) {
$differOptions = $this->differOptions;
}
if (empty($renderOptions)) {
$renderOptions = $this->renderOptions;
}
$fromContents = $this->fromVersion->contents;
$toContents = $this->toVersion->contents;
$diff = [];
$createDiff = function ($key, $old, $new) use (&$diff, $renderer, $differOptions, $renderOptions) {
if ($renderer) {
$old = is_string($old) ? $old : json_encode($old);
$new = is_string($new) ? $new : json_encode($new);
if ($old === $new) {
// If there are no changes, write a hardcoded custom diff.
$diff[$key] = '<table class="diff-wrapper diff diff-html diff-side-by-side"><tbody class="change change-eq"><tr><td class="old">'.$old.'</td><td class="new">'.$new.'</td></tr></tbody></table>';
} else {
$diff[$key] = str_replace('\n No newline at end of file', '', DiffHelper::calculate($old, $new, $renderer, $differOptions, $renderOptions));
}
} else {
$diff[$key] = compact('old', 'new');
}
};
// we are not using ArrayDiffMultidimensional, as we dont need to strip out the identical string. so a simple foreach on our attributes
foreach ($fromContents as $key => $value) {
$createDiff($key, Arr::get($toContents, $key), Arr::get($fromContents, $key));
}
foreach (array_diff_key($fromContents, $toContents) as $key => $value) {
$createDiff($key, null, $value);
}
return $diff;
}
I have opened an issue on PHP Diff package: https://github.com/jfcherng/php-diff/issues/78
I have hacked your Diff => render method by manually creating a simple diff for identical strings 😈
public function render(string $renderer = null, array $differOptions = [], array $renderOptions = []): array { if (empty($differOptions)) { $differOptions = $this->differOptions; } if (empty($renderOptions)) { $renderOptions = $this->renderOptions; } $fromContents = $this->fromVersion->contents; $toContents = $this->toVersion->contents; $diff = []; $createDiff = function ($key, $old, $new) use (&$diff, $renderer, $differOptions, $renderOptions) { if ($renderer) { $old = is_string($old) ? $old : json_encode($old); $new = is_string($new) ? $new : json_encode($new); if ($old === $new) { // If there are no changes, write a hardcoded custom diff. $diff[$key] = '<table class="diff-wrapper diff diff-html diff-side-by-side"><tbody class="change change-eq"><tr><td class="old">'.$old.'</td><td class="new">'.$new.'</td></tr></tbody></table>'; } else { $diff[$key] = str_replace('\n No newline at end of file', '', DiffHelper::calculate($old, $new, $renderer, $differOptions, $renderOptions)); } } else { $diff[$key] = compact('old', 'new'); } }; // we are not using ArrayDiffMultidimensional, as we dont need to strip out the identical string. so a simple foreach on our attributes foreach ($fromContents as $key => $value) { $createDiff($key, Arr::get($toContents, $key), Arr::get($fromContents, $key)); } foreach (array_diff_key($fromContents, $toContents) as $key => $value) { $createDiff($key, null, $value); } return $diff; }
5.x branch seems ready, do you need add this to 5.x ?
I have opened an issue on PHP Diff package: jfcherng/php-diff#78
This request was completed by the Diff author 🚀
@overtrue This how your render
would look now:
public function render(string $renderer = null, array $differOptions = [], array $renderOptions = []): array
{
if (empty($differOptions)) {
$differOptions = $this->differOptions;
}
if (empty($renderOptions)) {
$renderOptions = $this->renderOptions;
}
$fromContents = $this->fromVersion->contents;
$toContents = $this->toVersion->contents;
$diff = [];
$createDiff = function ($key, $old, $new) use (&$diff, $renderer, $differOptions, $renderOptions) {
if ($renderer) {
$old = is_string($old) ? $old : json_encode($old);
$new = is_string($new) ? $new : json_encode($new);
$diff[$key] = str_replace('\n No newline at end of file', '', DiffHelper::calculate($old, $new, $renderer, $differOptions, $renderOptions));
} else {
$diff[$key] = compact('old', 'new');
}
};
// we are not using ArrayDiffMultidimensional, as we dont need to strip out the identical string. so a simple foreach on our attributes
foreach ($fromContents as $key => $value) {
$createDiff($key, Arr::get($toContents, $key), Arr::get($fromContents, $key));
}
foreach (array_diff_key($fromContents, $toContents) as $key => $value) {
$createDiff($key, null, $value);
}
return $diff;
}
Great!
The VersionStrategy::SNAPSHOT saves all the Model attributes instead of saving on the ones specified using:
I think this is because of
$this->getAttributes()
in following code?It should be
$changedKeys = array_keys($this->getVersionable());
?Or we can even add a completely new strategy?