diff --git a/README.md b/README.md index 57f119d..e61e5a3 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,93 @@ All timestamps and the optional soft-delete timestamp will be ignored. +### Using JSON encoding + +Run + +``` +php artisan vendor:publish --provider="Mpociot\Versionable\Providers\ServiceProvider" --tag="config" +``` + +Adjust the encoding in the config from `serialize` to `json`. + +Create and run a migration like the following: + +``` + '{', + 'json' => 'a:', + ]; + + protected $chunkSize = 10; + + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + $targetEncoding = config('versionable.encoding'); + $sourceEncoding = $targetEncoding === 'json' ? 'serialize' : 'json'; + + $this->changeEncoding($targetEncoding, $sourceEncoding); + + Schema::table('versions', function ($table) { + $table->json('model_data')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $sourceEncoding = config('versionable.encoding'); + $targetEncoding = $sourceEncoding === 'json' ? 'serialize' : 'json'; + + $this->changeEncoding($targetEncoding, $sourceEncoding); + + Schema::table('versions', function ($table) { + $table->longText('model_data')->change(); + }); + } + + protected function changeEncoding($targetEncoding, $sourceEncoding) + { + $versions = Version::lazy($this->chunkSize); + + foreach ($versions as $version) { + $this->validateData($version, $sourceEncoding); + + $version->model_data = $targetEncoding === 'serialize' + ? serialize(json_decode($version->model_data, true)) + : json_encode(unserialize($version->model_data)); + + $version->save(); + } + + return true; + } + + protected function validateData(Version $version, $sourceEncoding) + { + if (strpos($version->model_data, $this->encodingCheck[$sourceEncoding]) === 0) { + throw new RuntimeException("Wrong source encoding while trying to convert from '$sourceEncoding': ".substr($version->model_data, 0, 10).'..'); + } + } +} +``` + ### Exclude attributes from versioning 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. diff --git a/src/Mpociot/Versionable/Version.php b/src/Mpociot/Versionable/Version.php index d145eb1..7f76984 100644 --- a/src/Mpociot/Versionable/Version.php +++ b/src/Mpociot/Versionable/Version.php @@ -31,6 +31,15 @@ public function versionable() return $this->morphTo(); } + /** + * Return the encoding + * @return mixed + */ + private function getEncoding() + { + return config('versionable.encoding', 'serialize'); + } + /** * Return the user responsible for this version * @return mixed @@ -50,10 +59,11 @@ public function getModel() $modelData = is_resource($this->model_data) ? stream_get_contents($this->model_data,-1,0) : $this->model_data; + $modelDataEncoded = $this->getEncoding() === 'json' ? json_decode($modelData, true) : unserialize($modelData); $model = new $this->versionable_type(); $model->unguard(); - $model->fill(unserialize($modelData)); + $model->fill($modelDataEncoded); $model->exists = true; $model->reguard(); return $model; diff --git a/src/Mpociot/Versionable/VersionableTrait.php b/src/Mpociot/Versionable/VersionableTrait.php index 9be633a..0872f26 100644 --- a/src/Mpociot/Versionable/VersionableTrait.php +++ b/src/Mpociot/Versionable/VersionableTrait.php @@ -14,7 +14,7 @@ trait VersionableTrait /** * Retrieve, if exists, the property that define that Version model. * If no property defined, use the default Version model. - * + * * Trait cannot share properties whth their class ! * http://php.net/manual/en/language.oop5.traits.php * @return unknown|string @@ -28,6 +28,16 @@ protected function getVersionClass() return config('versionable.version_model', Version::class); } + /** + * Get the encoding, the default is serialize. + * + * @return string + */ + protected function getEncoding() + { + return config('versionable.encoding', 'serialize'); + } + /** * Private variable to detect if this is an update * or an insert @@ -173,10 +183,12 @@ protected function versionablePostSave() $version->versionable_id = $this->getKey(); $version->versionable_type = method_exists($this, 'getMorphClass') ? $this->getMorphClass() : get_class($this); $version->user_id = $this->getAuthUserId(); - + $versionedHiddenFields = $this->versionedHiddenFields ?? []; $this->makeVisible($versionedHiddenFields); - $version->model_data = serialize($this->attributesToArray()); + $version->model_data = $this->getEncoding() === 'json' + ? json_encode($this->attributesToArray()) + : serialize($this->attributesToArray()); $this->makeHidden($versionedHiddenFields); if (!empty( $this->reason )) { @@ -191,16 +203,16 @@ protected function versionablePostSave() /** * Delete old versions of this model when the reach a specific count. - * + * * @return void */ private function purgeOldVersions() { $keep = isset($this->keepOldVersions) ? $this->keepOldVersions : 0; - + if ((int)$keep > 0) { $count = $this->versions()->count(); - + if ($count > $keep) { $this->getLatestVersions() ->take($count) diff --git a/src/config/config.php b/src/config/config.php index 637864c..d50e620 100644 --- a/src/config/config.php +++ b/src/config/config.php @@ -7,6 +7,11 @@ * Feel free to change this, if you need specific version * model logic. */ - 'version_model' => \Mpociot\Versionable\Version::class + 'version_model' => \Mpociot\Versionable\Version::class, -]; \ No newline at end of file + /* + * The encoding to use for the model data encoding. + * Default is 'serialize' and uses PHP serialize() but 'json' is also supported + */ + 'encoding' => 'serialize', +]; diff --git a/tests/VersionableTest.php b/tests/VersionableTest.php index 286f26f..93c32db 100644 --- a/tests/VersionableTest.php +++ b/tests/VersionableTest.php @@ -460,19 +460,19 @@ public function testKeepMaxVersionCount() $name_v2 = 'second' ; $name_v3 = 'third' ; $name_v4 = 'fourth' ; - + $model = new ModelWithMaxVersions(); $model->email = "m.pociot@test.php"; $model->password = "foo"; $model->name = $name_v1 ; $model->save(); - + $model->name = $name_v2 ; $model->save(); - + $model->name = $name_v3 ; $model->save(); - + $model->name = $name_v4 ; $model->save(); @@ -513,7 +513,24 @@ public function testAllowHiddenFields() { $this->assertArrayNotHasKey('password', $user->toArray()); } - + + public function testVersionWithJson() + { + $this->app['config']->set('versionable.encoding', 'json'); + + $user = new TestVersionableUser(); + $user->name = "Marcel"; + $user->email = "m.pociot@test.php"; + $user->password = "12345"; + $user->last_login = $user->freshTimestamp(); + $user->save(); + + $version = $user->currentVersion(); + + $this->assertStringStartsWith( '{', $version->model_data, 'Model data is not json encoded' ); + $this->assertEquals( $user->attributesToArray(), $version->getModel()->attributesToArray() ); + } + }