Skip to content

Commit

Permalink
Merge branch 'eugen-stranz-feat/ability_to_encrypt_eloquent_cast'
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Jun 13, 2024
2 parents c42a439 + 59586d0 commit 691f909
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 2 deletions.
15 changes: 15 additions & 0 deletions docs/advanced-usage/eloquent-casting.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,18 @@ $artist = Artist::create([
$artist->songs; // DataCollection
$artist->songs->count();// 0
```

## Using encryption with data objects and collections

Similar to Laravel's native encrypted casts, you can also encrypt data objects and collections.

When retrieving the model, the data object will be decrypted automatically.

```php
class Artist extends Model
{
protected $casts = [
'songs' => DataCollection::class.':'.SongData::class.',encrypted',
];
}
```
13 changes: 12 additions & 1 deletion src/Support/EloquentCasts/DataCollectionEloquentCast.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Facades\Crypt;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\LaravelData\Contracts\BaseDataCollectable;
use Spatie\LaravelData\Contracts\TransformableData;
Expand All @@ -25,6 +26,10 @@ public function __construct(

public function get($model, string $key, $value, array $attributes): ?DataCollection
{
if (is_string($value) && in_array('encrypted', $this->arguments)) {
$value = Crypt::decryptString($value);
}

if ($value === null && in_array('default', $this->arguments)) {
$value = '[]';
}
Expand Down Expand Up @@ -91,7 +96,13 @@ public function set($model, string $key, $value, array $attributes): ?string

$dataCollection = new ($this->dataCollectionClass)($this->dataClass, $data);

return $dataCollection->toJson();
$dataCollection = $dataCollection->toJson();

if (in_array('encrypted', $this->arguments)) {
return Crypt::encryptString($dataCollection);
}

return $dataCollection;
}

protected function isAbstractClassCast(): bool
Expand Down
13 changes: 12 additions & 1 deletion src/Support/EloquentCasts/DataEloquentCast.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\LaravelData\Support\EloquentCasts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Facades\Crypt;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\LaravelData\Contracts\TransformableData;
use Spatie\LaravelData\Exceptions\CannotCastData;
Expand All @@ -23,6 +24,10 @@ public function __construct(

public function get($model, string $key, $value, array $attributes): ?BaseData
{
if (is_string($value) && in_array('encrypted', $this->arguments)) {
$value = Crypt::decryptString($value);
}

if (is_null($value) && in_array('default', $this->arguments)) {
$value = '{}';
}
Expand Down Expand Up @@ -70,7 +75,13 @@ public function set($model, string $key, $value, array $attributes): ?string
]);
}

return $value->toJson();
$value = $value->toJson();

if (in_array('encrypted', $this->arguments)) {
return Crypt::encryptString($value);
}

return $value;
}

protected function isAbstractClassCast(): bool
Expand Down
19 changes: 19 additions & 0 deletions tests/Fakes/Models/DummyModelWithEncryptedCasts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Spatie\LaravelData\Tests\Fakes\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\LaravelData\Tests\Fakes\SimpleData;
use Spatie\LaravelData\Tests\Fakes\SimpleDataCollection;

class DummyModelWithEncryptedCasts extends Model
{
protected $casts = [
'data' => SimpleData::class.':encrypted',
'data_collection' => SimpleDataCollection::class.':'.SimpleData::class.',encrypted',
];

protected $table = 'dummy_model_with_casts';

public $timestamps = false;
}
35 changes: 35 additions & 0 deletions tests/Support/EloquentCasts/DataEloquentCastTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;

use function Pest\Laravel\assertDatabaseHas;
Expand All @@ -9,6 +11,7 @@
use Spatie\LaravelData\Tests\Fakes\AbstractData\AbstractDataB;
use Spatie\LaravelData\Tests\Fakes\Models\DummyModelWithCasts;
use Spatie\LaravelData\Tests\Fakes\Models\DummyModelWithDefaultCasts;
use Spatie\LaravelData\Tests\Fakes\Models\DummyModelWithEncryptedCasts;
use Spatie\LaravelData\Tests\Fakes\SimpleData;
use Spatie\LaravelData\Tests\Fakes\SimpleDataWithDefaultValue;

Expand Down Expand Up @@ -130,3 +133,35 @@
->toBeInstanceOf(AbstractDataA::class)
->a->toBe('A\A');
});

it('can save an encrypted data object', function () {
// Save the encrypted data to the database
DummyModelWithEncryptedCasts::create([
'data' => new SimpleData('Test'),
]);

// Retrieve the model from the database without Eloquent casts
$model = DB::table('dummy_model_with_casts')
->first();

try {
Crypt::decryptString($model->data);
$isEncrypted = true;
} catch (DecryptException $e) {
$isEncrypted = false;
}

expect($isEncrypted)->toBeTrue();
});

it('can load an encrypted data object', function () {
// Save the encrypted data to the database
DummyModelWithEncryptedCasts::create([
'data' => new SimpleData('Test'),
]);

/** @var \Spatie\LaravelData\Tests\Fakes\Models\DummyModelWithCasts $model */
$model = DummyModelWithEncryptedCasts::first();

expect($model->data)->toEqual(new SimpleData('Test'));
});

0 comments on commit 691f909

Please sign in to comment.