Skip to content

Commit

Permalink
[9.x] Support objects like GMP for custom Model casts (#43959)
Browse files Browse the repository at this point in the history
* Use isset to support object like GMP

* wip

* Apply fixes from StyleCI

* wip

* wip

* Update src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php

Co-authored-by: StyleCI Bot <[email protected]>
  • Loading branch information
driesvints and StyleCIBot authored Sep 1, 2022
1 parent 0efbd7e commit 4431fb3
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 6 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis-phpredis/[email protected], igbinary, msgpack, lzf, zstd, lz4, memcached
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis-phpredis/[email protected], igbinary, msgpack, lzf, zstd, lz4, memcached, gmp
ini-values: error_reporting=E_ALL
tools: composer:v2
coverage: none
Expand Down Expand Up @@ -122,7 +122,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, gd, pdo_mysql, fileinfo, ftp, redis, memcached
extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, gd, pdo_mysql, fileinfo, ftp, redis, memcached, gmp
tools: composer:v2
coverage: none

Expand Down
8 changes: 4 additions & 4 deletions src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,11 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt
// If the attribute cast was a date or a datetime, we will serialize the date as
// a string. This allows the developers to customize how dates are serialized
// into an array without affecting how they are persisted into the storage.
if ($attributes[$key] && in_array($value, ['date', 'datetime', 'immutable_date', 'immutable_datetime'])) {
if (isset($attributes[$key]) && in_array($value, ['date', 'datetime', 'immutable_date', 'immutable_datetime'])) {
$attributes[$key] = $this->serializeDate($attributes[$key]);
}

if ($attributes[$key] && ($this->isCustomDateTimeCast($value) ||
if (isset($attributes[$key]) && ($this->isCustomDateTimeCast($value) ||
$this->isImmutableCustomDateTimeCast($value))) {
$attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
}
Expand All @@ -303,7 +303,7 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt
$attributes[$key] = $this->serializeDate($attributes[$key]);
}

if ($attributes[$key] && $this->isClassSerializable($key)) {
if (isset($attributes[$key]) && $this->isClassSerializable($key)) {
$attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]);
}

Expand Down Expand Up @@ -930,7 +930,7 @@ public function setAttribute($key, $value)
// If an attribute is listed as a "date", we'll convert it from a DateTime
// instance into a form proper for storage on the database tables using
// the connection grammar's date format. We will auto set the values.
elseif ($value && $this->isDateAttribute($key)) {
elseif (! is_null($value) && $this->isDateAttribute($key)) {
$value = $this->fromDateTime($value);
}

Expand Down
77 changes: 77 additions & 0 deletions tests/Integration/Database/EloquentModelCustomCastingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Illuminate\Tests\Integration\Database;

use GMP;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes;
use Illuminate\Database\Capsule\Manager as DB;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Schema\Blueprint;
Expand Down Expand Up @@ -40,6 +42,7 @@ public function createSchema()
$table->increments('id');
$table->string('address_line_one');
$table->string('address_line_two');
$table->integer('amount');
$table->string('string_field');
$table->timestamps();
});
Expand All @@ -63,6 +66,7 @@ public function testSavingCastedAttributesToDatabase()
/** @var \Illuminate\Tests\Integration\Database\CustomCasts $model */
$model = CustomCasts::create([
'address' => new AddressModel('address_line_one_value', 'address_line_two_value'),
'amount' => gmp_init('1000', 10),
'string_field' => null,
]);

Expand All @@ -72,6 +76,8 @@ public function testSavingCastedAttributesToDatabase()
$this->assertSame('address_line_two_value', $model->getOriginal('address_line_two'));
$this->assertSame('address_line_two_value', $model->getAttribute('address_line_two'));

$this->assertSame('1000', $model->getRawOriginal('amount'));

$this->assertNull($model->getOriginal('string_field'));
$this->assertNull($model->getAttribute('string_field'));
$this->assertSame('', $model->getRawOriginal('string_field'));
Expand All @@ -80,20 +86,23 @@ public function testSavingCastedAttributesToDatabase()
$another_model = CustomCasts::create([
'address_line_one' => 'address_line_one_value',
'address_line_two' => 'address_line_two_value',
'amount' => gmp_init('500', 10),
'string_field' => 'string_value',
]);

$this->assertInstanceOf(AddressModel::class, $another_model->address);

$this->assertSame('address_line_one_value', $model->address->lineOne);
$this->assertSame('address_line_two_value', $model->address->lineTwo);
$this->assertInstanceOf(GMP::class, $model->amount);
}

public function testInvalidArgumentExceptionOnInvalidValue()
{
/** @var \Illuminate\Tests\Integration\Database\CustomCasts $model */
$model = CustomCasts::create([
'address' => new AddressModel('address_line_one_value', 'address_line_two_value'),
'amount' => gmp_init('1000', 10),
'string_field' => 'string_value',
]);

Expand All @@ -111,6 +120,7 @@ public function testInvalidArgumentExceptionOnNull()
/** @var \Illuminate\Tests\Integration\Database\CustomCasts $model */
$model = CustomCasts::create([
'address' => new AddressModel('address_line_one_value', 'address_line_two_value'),
'amount' => gmp_init('1000', 10),
'string_field' => 'string_value',
]);

Expand All @@ -123,6 +133,27 @@ public function testInvalidArgumentExceptionOnNull()
$this->assertSame('address_line_two_value', $model->address->lineTwo);
}

public function testModelsWithCustomCastsCanBeConvertedToArrays()
{
/** @var \Illuminate\Tests\Integration\Database\CustomCasts $model */
$model = CustomCasts::create([
'address' => new AddressModel('address_line_one_value', 'address_line_two_value'),
'amount' => gmp_init('1000', 10),
'string_field' => 'string_value',
]);

// Ensure model values remain unchanged
$this->assertSame([
'address_line_one' => 'address_line_one_value',
'address_line_two' => 'address_line_two_value',
'amount' => '1000',
'string_field' => 'string_value',
'updated_at' => $model->updated_at->toJSON(),
'created_at' => $model->created_at->toJSON(),
'id' => 1,
], $model->toArray());
}

/**
* Get a database connection instance.
*
Expand Down Expand Up @@ -188,6 +219,51 @@ public function set($model, $key, $value, $attributes)
}
}

class GMPCast implements CastsAttributes, SerializesCastableAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param string $value
* @param array $attributes
* @return string|null
*/
public function get($model, $key, $value, $attributes)
{
return gmp_init($value, 10);
}

/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param string|null $value
* @param array $attributes
* @return string
*/
public function set($model, $key, $value, $attributes)
{
return gmp_strval($value, 10);
}

/**
* Serialize the attribute when converting the model to an array.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
*/
public function serialize($model, string $key, $value, array $attributes)
{
return gmp_strval($value, 10);
}
}

class NonNullableString implements CastsAttributes
{
/**
Expand Down Expand Up @@ -239,6 +315,7 @@ class CustomCasts extends Eloquent
*/
protected $casts = [
'address' => AddressCast::class,
'amount' => GMPCast::class,
'string_field' => NonNullableString::class,
];
}
Expand Down

0 comments on commit 4431fb3

Please sign in to comment.