Skip to content

Commit 8c29cd1

Browse files
committed
add json support
1 parent 043b311 commit 8c29cd1

File tree

5 files changed

+149
-14
lines changed

5 files changed

+149
-14
lines changed

README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,97 @@ All timestamps and the optional soft-delete timestamp will be ignored.
6969

7070
<a name="exclude" />
7171

72+
### Using JSON encoding
73+
74+
Run
75+
76+
```
77+
php artisan vendor:publish --provider="Mpociot\Versionable\Providers\ServiceProvider" --tag="config"
78+
```
79+
80+
Adjust the encoding in the config from `serialize` to `json`.
81+
82+
Create a migration like the following:
83+
84+
```
85+
<?php
86+
87+
use Illuminate\Database\Migrations\Migration;
88+
use Mpociot\Versionable\Version;
89+
90+
class Encoding extends Migration
91+
{
92+
protected $encodingCheck = [
93+
'serialize' => '{',
94+
'json' => 'a:',
95+
];
96+
97+
protected $chunkSize = 10;
98+
99+
/**
100+
* Run the migrations.
101+
*
102+
* @return void
103+
*/
104+
public function up()
105+
{
106+
$targetEncoding = config('versionable.encoding');
107+
$sourceEncoding = $targetEncoding === 'json' ? 'serialize' : 'json';
108+
109+
$this->changeEncoding($targetEncoding, $sourceEncoding);
110+
111+
Schema::table('versions', function ($table) {
112+
$table->json('model_data')->change();
113+
});
114+
}
115+
116+
/**
117+
* Reverse the migrations.
118+
*
119+
* @return void
120+
*/
121+
public function down()
122+
{
123+
$sourceEncoding = config('versionable.encoding');
124+
$targetEncoding = $sourceEncoding === 'json' ? 'serialize' : 'json';
125+
126+
$this->changeEncoding($targetEncoding, $sourceEncoding);
127+
128+
Schema::table('versions', function ($table) {
129+
$table->longText('model_data')->change();
130+
});
131+
}
132+
133+
protected function changeEncoding($targetEncoding, $sourceEncoding)
134+
{
135+
$versions = Version::lazy($this->chunkSize);
136+
137+
foreach ($versions as $version) {
138+
if (!$this->validateData($version, $sourceEncoding)) {
139+
throw new RuntimeException("Wrong source encoding while trying to convert from '$sourceEncoding' to'$targetEncoding'");
140+
}
141+
142+
$version->model_data = $targetEncoding === 'serialize'
143+
? serialize(json_decode($version->model_data, true))
144+
: json_encode(unserialize($version->model_data));
145+
146+
$version->save();
147+
}
148+
149+
return true;
150+
}
151+
152+
protected function validateData(Version $version, $sourceEncoding)
153+
{
154+
if (strpos($version->model_data, $this->encodingCheck[$sourceEncoding]) === 0) {
155+
return false;
156+
}
157+
158+
return true;
159+
}
160+
}
161+
```
162+
72163
### Exclude attributes from versioning
73164

74165
Sometimes you don't want to create a version *every* time an attribute on your model changes. For example your User model might have a `last_login_at` attribute.

src/Mpociot/Versionable/Version.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ public function versionable()
3131
return $this->morphTo();
3232
}
3333

34+
/**
35+
* Return the encoding
36+
* @return mixed
37+
*/
38+
private function getEncoding()
39+
{
40+
return config('versionable.encoding', 'serialize');
41+
}
42+
3443
/**
3544
* Return the user responsible for this version
3645
* @return mixed
@@ -50,10 +59,11 @@ public function getModel()
5059
$modelData = is_resource($this->model_data)
5160
? stream_get_contents($this->model_data,-1,0)
5261
: $this->model_data;
62+
$modelDataEncoded = $this->getEncoding() === 'json' ? json_decode($modelData, true) : unserialize($modelData);
5363

5464
$model = new $this->versionable_type();
5565
$model->unguard();
56-
$model->fill(unserialize($modelData));
66+
$model->fill($modelDataEncoded);
5767
$model->exists = true;
5868
$model->reguard();
5969
return $model;

src/Mpociot/Versionable/VersionableTrait.php

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ trait VersionableTrait
1414
/**
1515
* Retrieve, if exists, the property that define that Version model.
1616
* If no property defined, use the default Version model.
17-
*
17+
*
1818
* Trait cannot share properties whth their class !
1919
* http://php.net/manual/en/language.oop5.traits.php
2020
* @return unknown|string
@@ -28,6 +28,16 @@ protected function getVersionClass()
2828
return config('versionable.version_model', Version::class);
2929
}
3030

31+
/**
32+
* Get the encoding, the default is serialize.
33+
*
34+
* @return string
35+
*/
36+
protected function getEncoding()
37+
{
38+
return config('versionable.encoding', 'serialize');
39+
}
40+
3141
/**
3242
* Private variable to detect if this is an update
3343
* or an insert
@@ -173,10 +183,12 @@ protected function versionablePostSave()
173183
$version->versionable_id = $this->getKey();
174184
$version->versionable_type = method_exists($this, 'getMorphClass') ? $this->getMorphClass() : get_class($this);
175185
$version->user_id = $this->getAuthUserId();
176-
186+
177187
$versionedHiddenFields = $this->versionedHiddenFields ?? [];
178188
$this->makeVisible($versionedHiddenFields);
179-
$version->model_data = serialize($this->attributesToArray());
189+
$version->model_data = $this->getEncoding() === 'json'
190+
? json_encode($this->attributesToArray())
191+
: serialize($this->attributesToArray());
180192
$this->makeHidden($versionedHiddenFields);
181193

182194
if (!empty( $this->reason )) {
@@ -191,16 +203,16 @@ protected function versionablePostSave()
191203

192204
/**
193205
* Delete old versions of this model when the reach a specific count.
194-
*
206+
*
195207
* @return void
196208
*/
197209
private function purgeOldVersions()
198210
{
199211
$keep = isset($this->keepOldVersions) ? $this->keepOldVersions : 0;
200-
212+
201213
if ((int)$keep > 0) {
202214
$count = $this->versions()->count();
203-
215+
204216
if ($count > $keep) {
205217
$this->getLatestVersions()
206218
->take($count)

src/config/config.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
* Feel free to change this, if you need specific version
88
* model logic.
99
*/
10-
'version_model' => \Mpociot\Versionable\Version::class
10+
'version_model' => \Mpociot\Versionable\Version::class,
1111

12-
];
12+
/*
13+
* The encoding to use for the model data encoding.
14+
* Default is 'serialize' and uses PHP serialize() but 'json' is also supported
15+
*/
16+
'encoding' => 'serialize',
17+
];

tests/VersionableTest.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -460,19 +460,19 @@ public function testKeepMaxVersionCount()
460460
$name_v2 = 'second' ;
461461
$name_v3 = 'third' ;
462462
$name_v4 = 'fourth' ;
463-
463+
464464
$model = new ModelWithMaxVersions();
465465
$model->email = "m.pociot@test.php";
466466
$model->password = "foo";
467467
$model->name = $name_v1 ;
468468
$model->save();
469-
469+
470470
$model->name = $name_v2 ;
471471
$model->save();
472-
472+
473473
$model->name = $name_v3 ;
474474
$model->save();
475-
475+
476476
$model->name = $name_v4 ;
477477
$model->save();
478478

@@ -513,7 +513,24 @@ public function testAllowHiddenFields() {
513513

514514
$this->assertArrayNotHasKey('password', $user->toArray());
515515
}
516-
516+
517+
public function testVersionWithJson()
518+
{
519+
$this->app['config']->set('versionable.encoding', 'json');
520+
521+
$user = new TestVersionableUser();
522+
$user->name = "Marcel";
523+
$user->email = "m.pociot@test.php";
524+
$user->password = "12345";
525+
$user->last_login = $user->freshTimestamp();
526+
$user->save();
527+
528+
$version = $user->currentVersion();
529+
530+
$this->assertStringStartsWith( '{', $version->model_data, 'Model data is not json encoded' );
531+
$this->assertEquals( $user->attributesToArray(), $version->getModel()->attributesToArray() );
532+
}
533+
517534
}
518535

519536

0 commit comments

Comments
 (0)