From 242e2333d79ee9faa49688ffbcc38aeb71752a00 Mon Sep 17 00:00:00 2001 From: Lukas Kahwe Smith Date: Thu, 20 May 2021 15:26:30 +0200 Subject: [PATCH 1/2] add json support --- README.md | 87 ++++++++++++++++++++ src/Mpociot/Versionable/Version.php | 12 ++- src/Mpociot/Versionable/VersionableTrait.php | 24 ++++-- src/config/config.php | 9 +- tests/VersionableTest.php | 29 +++++-- 5 files changed, 146 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 251d5b6..785bb62 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,93 @@ If you want to do this for all instances of a model: +### 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 add5cba..860289a 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,11 +59,12 @@ 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); $className = self::getActualClassNameForMorph($this->versionable_type); $model = new $className(); $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 174096d..9281a9c 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 )) { @@ -233,16 +245,16 @@ public function createInitialVersion() /** * Delete old versions of this model when they 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 c87fa24..1d1bf83 100644 --- a/tests/VersionableTest.php +++ b/tests/VersionableTest.php @@ -461,19 +461,19 @@ public function testKeepMaxVersionCount() $name_v2 = 'second' ; $name_v3 = 'third' ; $name_v4 = 'fourth' ; - + $model = new ModelWithMaxVersions(); $model->email = "nono.ma@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(); @@ -562,7 +562,7 @@ public function testInitializeModel() $user = TestVersionableUser::find($user->id); $this->assertCount(0, $user->versions ); - + TestVersionableUser::initializeVersions(); $this->assertCount(1, $user->fresh()->versions ); @@ -571,7 +571,24 @@ public function testInitializeModel() $this->assertCount(1, $user->fresh()->versions ); } - + + + public function testVersionWithJson() + { + $this->app['config']->set('versionable.encoding', 'json'); + + $user = new TestVersionableUser(); + $user->name = "Nono"; + $user->email = "nono.ma@bmail.php"; + $user->password = "12345"; + $user->save(); + + $version = $user->currentVersion(); + + $this->assertStringStartsWith( '{', $version->model_data, 'Model data is not json encoded' ); + $this->assertEquals( $user->attributesToArray(), $version->getModel()->attributesToArray() ); + } + } From 9841341ed8880f5257ea3edf1f6df1708a0c82c8 Mon Sep 17 00:00:00 2001 From: Erik Gaal Date: Tue, 12 Jul 2022 00:09:40 +0200 Subject: [PATCH 2/2] refactor to strategy classes --- src/Mpociot/Versionable/Encoders/Encoder.php | 16 ++++++++++++++++ src/Mpociot/Versionable/Encoders/JsonEncoder.php | 16 ++++++++++++++++ .../Versionable/Encoders/SerializeEncoder.php | 16 ++++++++++++++++ src/Mpociot/Versionable/Version.php | 15 +++++++++------ src/Mpociot/Versionable/VersionableTrait.php | 14 +++++++------- src/config/config.php | 4 ++-- tests/VersionableTest.php | 2 +- 7 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 src/Mpociot/Versionable/Encoders/Encoder.php create mode 100644 src/Mpociot/Versionable/Encoders/JsonEncoder.php create mode 100644 src/Mpociot/Versionable/Encoders/SerializeEncoder.php diff --git a/src/Mpociot/Versionable/Encoders/Encoder.php b/src/Mpociot/Versionable/Encoders/Encoder.php new file mode 100644 index 0000000..5f5a302 --- /dev/null +++ b/src/Mpociot/Versionable/Encoders/Encoder.php @@ -0,0 +1,16 @@ +model_data) ? stream_get_contents($this->model_data,-1,0) : $this->model_data; - $modelDataEncoded = $this->getEncoding() === 'json' ? json_decode($modelData, true) : unserialize($modelData); + $modelDataDecoded = $this->getEncoder()->decode($modelData); $className = self::getActualClassNameForMorph($this->versionable_type); $model = new $className(); $model->unguard(); - $model->fill($modelDataEncoded); + $model->fill($modelDataDecoded); $model->exists = true; $model->reguard(); return $model; diff --git a/src/Mpociot/Versionable/VersionableTrait.php b/src/Mpociot/Versionable/VersionableTrait.php index 9281a9c..6573977 100644 --- a/src/Mpociot/Versionable/VersionableTrait.php +++ b/src/Mpociot/Versionable/VersionableTrait.php @@ -3,6 +3,8 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Mpociot\Versionable\Encoders\Encoder; +use Mpociot\Versionable\Encoders\SerializeEncoder; /** * Class VersionableTrait @@ -29,13 +31,13 @@ protected function getVersionClass() } /** - * Get the encoding, the default is serialize. + * Get the encoder. * - * @return string + * @return Encoder */ - protected function getEncoding() + protected function getEncoder(): Encoder { - return config('versionable.encoding', 'serialize'); + return app(config('versionable.encoder', SerializeEncoder::class)); } /** @@ -186,9 +188,7 @@ protected function versionablePostSave() $versionedHiddenFields = $this->versionedHiddenFields ?? []; $this->makeVisible($versionedHiddenFields); - $version->model_data = $this->getEncoding() === 'json' - ? json_encode($this->attributesToArray()) - : serialize($this->attributesToArray()); + $version->model_data = $this->getEncoder()->encode($this->attributesToArray()); $this->makeHidden($versionedHiddenFields); if (!empty( $this->reason )) { diff --git a/src/config/config.php b/src/config/config.php index d50e620..cf535cc 100644 --- a/src/config/config.php +++ b/src/config/config.php @@ -11,7 +11,7 @@ /* * The encoding to use for the model data encoding. - * Default is 'serialize' and uses PHP serialize() but 'json' is also supported */ - 'encoding' => 'serialize', + 'encoder' => \Mpociot\Versionable\Encoders\SerializeEncoder::class, + ]; diff --git a/tests/VersionableTest.php b/tests/VersionableTest.php index 1d1bf83..7accab6 100644 --- a/tests/VersionableTest.php +++ b/tests/VersionableTest.php @@ -575,7 +575,7 @@ public function testInitializeModel() public function testVersionWithJson() { - $this->app['config']->set('versionable.encoding', 'json'); + $this->app['config']->set('versionable.encoder', \Mpociot\Versionable\Encoders\JsonEncoder::class); $user = new TestVersionableUser(); $user->name = "Nono";