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/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 @@ +morphTo(); } + /** + * Get the encoder. + * + * @return Encoder + */ + private function getEncoder(): Encoder + { + return app(config('versionable.encoder', SerializeEncoder::class)); + } + /** * Return the user responsible for this version * @return mixed @@ -50,11 +62,12 @@ public function getModel() $modelData = is_resource($this->model_data) ? stream_get_contents($this->model_data,-1,0) : $this->model_data; + $modelDataDecoded = $this->getEncoder()->decode($modelData); $className = self::getActualClassNameForMorph($this->versionable_type); $model = new $className(); $model->unguard(); - $model->fill(unserialize($modelData)); + $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 174096d..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 @@ -14,7 +16,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 +30,16 @@ protected function getVersionClass() return config('versionable.version_model', Version::class); } + /** + * Get the encoder. + * + * @return Encoder + */ + protected function getEncoder(): Encoder + { + return app(config('versionable.encoder', SerializeEncoder::class)); + } + /** * Private variable to detect if this is an update * or an insert. @@ -173,10 +185,10 @@ 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->getEncoder()->encode($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..cf535cc 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. + */ + 'encoder' => \Mpociot\Versionable\Encoders\SerializeEncoder::class, + +]; diff --git a/tests/VersionableTest.php b/tests/VersionableTest.php index c87fa24..7accab6 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.encoder', \Mpociot\Versionable\Encoders\JsonEncoder::class); + + $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() ); + } + }