Skip to content

Commit

Permalink
[10.x] Add 'hashed' cast (#46947)
Browse files Browse the repository at this point in the history
* Add 'hashed' cast

* Fix linting issues
  • Loading branch information
gdebrauwer authored May 4, 2023
1 parent 51251d4 commit 1c783fe
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Illuminate\Support\Exceptions\MathException;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use InvalidArgumentException;
use LogicException;
Expand Down Expand Up @@ -104,6 +105,7 @@ trait HasAttributes
'encrypted:json',
'encrypted:object',
'float',
'hashed',
'immutable_date',
'immutable_datetime',
'immutable_custom_datetime',
Expand Down Expand Up @@ -985,6 +987,10 @@ public function setAttribute($key, $value)
$value = $this->castAttributeAsEncryptedString($key, $value);
}

if (! is_null($value) && $this->hasCast($key, 'hashed')) {
$value = $this->castAttributeAsHashedString($key, $value);
}

$this->attributes[$key] = $value;

return $this;
Expand Down Expand Up @@ -1293,6 +1299,18 @@ public static function encryptUsing($encrypter)
static::$encrypter = $encrypter;
}

/**
* Cast the given attribute to a hashed string.
*
* @param string $key
* @param mixed $value
* @return string
*/
protected function castAttributeAsHashedString($key, $value)
{
return Hash::needsRehash($value) ? Hash::make($value) : $value;
}

/**
* Decode the given float.
*
Expand Down
91 changes: 91 additions & 0 deletions tests/Integration/Database/EloquentModelHashedCastingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

namespace Illuminate\Tests\Integration\Database;

use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;

class EloquentModelHashedCastingTest extends DatabaseTestCase
{
protected $hasher;

protected function setUp(): void
{
parent::setUp();

$this->hasher = $this->mock(Hasher::class);
Hash::swap($this->hasher);
}

protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
{
Schema::create('hashed_casts', function (Blueprint $table) {
$table->increments('id');
$table->string('password')->nullable();
});
}

public function testHashed()
{
$this->hasher->expects('needsRehash')
->with('this is a password')
->andReturnTrue();

$this->hasher->expects('make')
->with('this is a password')
->andReturn('hashed-password');

$subject = HashedCast::create([
'password' => 'this is a password',
]);

$this->assertSame('hashed-password', $subject->password);
$this->assertDatabaseHas('hashed_casts', [
'id' => $subject->id,
'password' => 'hashed-password',
]);
}

public function testNotHashedIfAlreadyHashed()
{
$this->hasher->expects('needsRehash')
->with('already-hashed-password')
->andReturnFalse();

$subject = HashedCast::create([
'password' => 'already-hashed-password',
]);

$this->assertSame('already-hashed-password', $subject->password);
$this->assertDatabaseHas('hashed_casts', [
'id' => $subject->id,
'password' => 'already-hashed-password',
]);
}

public function testNotHashedIfNull()
{
$subject = HashedCast::create([
'password' => null,
]);

$this->assertNull($subject->password);
$this->assertDatabaseHas('hashed_casts', [
'id' => $subject->id,
'password' => null,
]);
}
}

class HashedCast extends Model
{
public $timestamps = false;
protected $guarded = [];

public $casts = [
'password' => 'hashed',
];
}

0 comments on commit 1c783fe

Please sign in to comment.