Skip to content

Commit 8199009

Browse files
authored
Merge pull request #1 from khepin/more-mutators
Additional Mutators
2 parents 2c14d1a + 8276d3d commit 8199009

File tree

7 files changed

+246
-4
lines changed

7 files changed

+246
-4
lines changed

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class User extends Model
4747
* {@inheritdoc}
4848
*/
4949
protected $table = 'users';
50-
50+
5151
/**
5252
* {@inheritdoc}
5353
*/
@@ -59,11 +59,20 @@ class User extends Model
5959

6060
This will automatically serialize/unserialize the `id` attribute on the `User` model when
6161
getting/setting the attribute from the database. This allows you to no longer need to set
62-
accessors/mutator methods on the model directly.
62+
accessors/mutator methods on the model directly.
6363

6464
> **Note:** Unlike the built in Laravel accessors/mutators,
6565
this package will serialize the attribute values when they are passed to an Eloquent query builder.
6666

67+
Included Mutators
68+
-----------------
69+
70+
- `uuid_v1_binary` Will take a uuid version 1, re-order its bytes so that if uuidA was generated before uuidB, then storedUuidA < storedUuidB, and store it in the database as 16 bytes of data. For more information on the re-ordering of bytes, see: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/.
71+
- `ip_binary` Will take a string representation of an IPv4 or IPv6 and store it as 16 bytes in the database.
72+
- `encrypt_string` Will take a non encrypted string and encrypt it when going to the database.
73+
- `hex_binary` Will take any hexadecimal string attribute and store it as binary data.
74+
- `unix_timestamp` Will take a Carbon date but store it as an integer unix timestamp.
75+
6776
Creating Custom Mutators
6877
------------------------
6978

@@ -74,7 +83,7 @@ To define a custom mutator, you'll need to create a class that implements
7483
any caching logic at the mutator level.
7584

7685
When building and registering a Mutator, it is important to know that they
77-
are resolved automatically from the Laravel IOC container, which means you may create
86+
are resolved automatically from the Laravel IOC container, which means you may create
7887
service providers for them if they require custom constructor arguments.
7988

8089
```php

config/config.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
'uuid_v1_binary' => \Weebly\Mutate\Mutators\UuidV1BinaryMutator::class,
99
'ip_binary' => \Weebly\Mutate\Mutators\IpBinaryMutator::class,
1010
'encrypt_string' => \Weebly\Mutate\Mutators\EncryptStringMutator::class,
11+
'hex_binary' => \Weebly\Mutate\Mutators\HexBinaryMutator::class,
12+
'unix_timestamp' => \Weebly\Mutate\Mutators\UnixTimestampMutator::class,
1113
],
1214
];

src/Mutators/HexBinaryMutator.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Weebly\Mutate\Mutators;
4+
5+
use Weebly\Mutate\Exceptions\MutateException;
6+
7+
/**
8+
* Presents an attribute as a hexadecimal string.
9+
* Stores the attribute as a byte array.
10+
*/
11+
class HexBinaryMutator implements MutatorContract
12+
{
13+
/**
14+
* {@inheritdoc}
15+
*/
16+
public function serializeAttribute($value)
17+
{
18+
if (! ctype_xdigit($value) || strlen($value) % 2 !== 0) {
19+
throw new MutateException(__METHOD__.' expects the value to be serialized to be a hexadecimal string.');
20+
}
21+
22+
return hex2bin($value);
23+
}
24+
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function unserializeAttribute($value)
29+
{
30+
if (! $value) {
31+
return;
32+
}
33+
34+
return bin2hex($value);
35+
}
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Weebly\Mutate\Mutators;
4+
5+
use Carbon\Carbon;
6+
use Weebly\Mutate\Exceptions\MutateException;
7+
8+
/**
9+
* Presents an attribute as Carbon date object (http://carbon.nesbot.com/)
10+
* Stores the attribute as unix epoch timestamp.
11+
*/
12+
class UnixTimestampMutator implements MutatorContract
13+
{
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
public function serializeAttribute($value)
18+
{
19+
if (! $value instanceof Carbon) {
20+
throw new MutateException(__METHOD__." expects a Carbon\Carbon value. Received: ".print_r($value, true));
21+
}
22+
23+
return $value->timestamp;
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function unserializeAttribute($value)
30+
{
31+
if (! is_int($value) && ! ctype_digit($value)) {
32+
return;
33+
}
34+
35+
return Carbon::createFromTimestamp($value);
36+
}
37+
}

tests/Integration/MutatorTest.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ public function setUp()
5050
});
5151

5252
DB::connection()->getPdo()->exec('ALTER TABLE test_model ADD id BINARY(16);');
53+
54+
Schema::create('timestamped_model', function ($table) {
55+
$table->increments('id');
56+
$table->string('name');
57+
$table->integer('created_at');
58+
$table->integer('updated_at');
59+
});
5360
}
5461

5562
public function test_where()
@@ -117,6 +124,18 @@ public function test_pluck_with_key()
117124
$ids = TestModel::where('id', $id)->pluck('id', 'location')->toArray();
118125
$this->assertEquals([$location => $id], $ids);
119126
}
127+
128+
public function test_timestamps()
129+
{
130+
(new TimestampedModel())->create(['name' => 'Model']);
131+
$model = TimestampedModel::first();
132+
$this->assertInstanceOf('Carbon\Carbon', $model->created_at);
133+
$this->assertInstanceOf('Carbon\Carbon', $model->updated_at);
134+
135+
$values = \DB::table('timestamped_model')->first();
136+
$this->assertTrue(ctype_digit($values->created_at));
137+
$this->assertTrue(ctype_digit($values->updated_at));
138+
}
120139
}
121140

122141
class TestModel extends Model
@@ -150,7 +169,19 @@ class TestModel extends Model
150169
* {@inheritdoc}
151170
*/
152171
protected $mutate = [
153-
'id' => 'uuid_v1_binary',
172+
'id' => 'uuid_v1_binary',
154173
'location' => 'encrypt_string',
155174
];
156175
}
176+
177+
class TimestampedModel extends Model
178+
{
179+
protected $table = 'timestamped_model';
180+
181+
protected $guarded = [];
182+
183+
protected $mutate = [
184+
'created_at' => 'unix_timestamp',
185+
'updated_at' => 'unix_timestamp',
186+
];
187+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace Weebly\Mutate\Mutators;
4+
5+
use stdClass;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class HexBinaryMutatorTest extends TestCase
9+
{
10+
/**
11+
* @param string $hex
12+
* @param string $expected
13+
* @dataProvider hexProvider
14+
*/
15+
public function testSerialize($hex, $expected)
16+
{
17+
$this->assertEquals($expected, (new HexBinaryMutator())->serializeAttribute($hex));
18+
}
19+
20+
/**
21+
* @param string $expected
22+
* @param string $binary
23+
* @dataProvider hexProvider
24+
*/
25+
public function testUnserialize($expected, $binary)
26+
{
27+
$this->assertEquals($expected, (new HexBinaryMutator())->unserializeAttribute($binary));
28+
}
29+
30+
/**
31+
* @return \Iterator
32+
*/
33+
public function hexProvider()
34+
{
35+
$hexes = [
36+
'Hex string of length 2' => ['e7', hex2bin('e7')],
37+
'Hex string of length 4' => ['232f', hex2bin('232f')],
38+
'Hex string of length 6' => ['0b76e0', hex2bin('0b76e0')],
39+
'Hex string of length 8' => ['65c1b7b0', hex2bin('65c1b7b0')],
40+
'Hex string of length 10' => ['e2f9995c0b', hex2bin('e2f9995c0b')],
41+
'Hex string of length 12' => ['cfc46177b9cd', hex2bin('cfc46177b9cd')],
42+
'Hex string of length 14' => ['01f4890a5996c1', hex2bin('01f4890a5996c1')],
43+
'Hex string of length 16' => ['d8d3a96d2a441b39', hex2bin('d8d3a96d2a441b39')],
44+
'Hex string of length 18' => ['5d87fca1c8f5c2ec21', hex2bin('5d87fca1c8f5c2ec21')],
45+
'Hex string of length 20' => ['c334821e50532bd40227', hex2bin('c334821e50532bd40227')],
46+
];
47+
48+
return $hexes;
49+
}
50+
51+
/**
52+
* @param mixed $value
53+
* @dataProvider notHexProvider
54+
* @expectedException \Weebly\Mutate\Exceptions\MutateException
55+
*/
56+
public function testWrongFormat($value)
57+
{
58+
(new HexBinaryMutator())->serializeAttribute($value);
59+
}
60+
61+
/**
62+
* @return array
63+
*/
64+
public function notHexProvider()
65+
{
66+
return [
67+
'An object cannot be serialized' => [new stdClass],
68+
'An int cannot be serialized' => [0],
69+
'Floats cannot be serialized' => [0.3],
70+
'Only hex strings can be serialiazed' => ['YzMzNDgyMWU1MDUzMmJkNDAy'],
71+
'Null cannot be serialized' => [null],
72+
'Booleans cannot be serialized' => [true],
73+
'Hex strings should have even lengths to be valid bytes representations' => ['a'],
74+
];
75+
}
76+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Weebly\Mutate\Mutators;
4+
5+
use Carbon\Carbon;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class UnixTimestampMutatorTest extends TestCase
9+
{
10+
/**
11+
* @param string $carbon
12+
* @param string $expected
13+
* @dataProvider carbonProvider
14+
*/
15+
public function testSerialize($carbon, $expected)
16+
{
17+
$this->assertEquals($expected, (new UnixTimestampMutator())->serializeAttribute($carbon));
18+
}
19+
20+
/**
21+
* @param string $expected
22+
* @param string $binary
23+
* @dataProvider carbonProvider
24+
*/
25+
public function testUnserialize($expected, $timestamp)
26+
{
27+
$unserialized = (new UnixTimestampMutator())->unserializeAttribute($timestamp);
28+
$this->assertEquals($expected->year, $unserialized->year);
29+
$this->assertEquals($expected->month, $unserialized->month);
30+
$this->assertEquals($expected->day, $unserialized->day);
31+
$this->assertEquals($expected->hour, $unserialized->hour);
32+
$this->assertEquals($expected->minute, $unserialized->minute);
33+
$this->assertEquals($expected->second, $unserialized->second);
34+
}
35+
36+
/**
37+
* @return \Iterator
38+
*/
39+
public function carbonProvider()
40+
{
41+
$now = time();
42+
43+
return [
44+
'Now' => [Carbon::createFromTimestamp($now), $now],
45+
'Timestamp 1' => [Carbon::createFromTimestamp(1), 1],
46+
'Timestamp for max uint32' => [Carbon::createFromTimestamp(pow(2, 32) - 1), pow(2, 32) - 1],
47+
'Negative timestamp' => [Carbon::createFromTimestamp(-200), -200],
48+
'Timestamp 0' => [Carbon::createFromTimestamp(0), 0],
49+
];
50+
}
51+
}

0 commit comments

Comments
 (0)