Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSON Support #83

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,93 @@ All timestamps and the optional soft-delete timestamp will be ignored.

<a name="exclude" />

### 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:

```
<?php

use Illuminate\Database\Migrations\Migration;
use Mpociot\Versionable\Version;

class Encoding extends Migration
{
protected $encodingCheck = [
'serialize' => '{',
'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.
Expand Down
12 changes: 11 additions & 1 deletion src/Mpociot/Versionable/Version.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down
24 changes: 18 additions & 6 deletions src/Mpociot/Versionable/VersionableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 )) {
Expand All @@ -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)
Expand Down
9 changes: 7 additions & 2 deletions src/config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,

];
/*
* The encoding to use for the model data encoding.
* Default is 'serialize' and uses PHP serialize() but 'json' is also supported
*/
'encoding' => 'serialize',
];
27 changes: 22 additions & 5 deletions tests/VersionableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -461,19 +461,19 @@ public function testKeepMaxVersionCount()
$name_v2 = 'second' ;
$name_v3 = 'third' ;
$name_v4 = 'fourth' ;

$model = new ModelWithMaxVersions();
$model->email = "[email protected]";
$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();

Expand Down Expand Up @@ -529,7 +529,24 @@ public function testWhereModelHasMorphMap()
$this->assertEquals( $user->attributesToArray(), $version->getModel()->attributesToArray() );
Relation::morphMap([], false);
}


public function testVersionWithJson()
{
$this->app['config']->set('versionable.encoding', 'json');

$user = new TestVersionableUser();
$user->name = "Marcel";
$user->email = "[email protected]";
$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() );
}

}


Expand Down