Skip to content

Commit

Permalink
Merge pull request #712 from DirectoryTree/BUG-707
Browse files Browse the repository at this point in the history
Bug 707 - Attribute casting seem to work only in one direction
  • Loading branch information
stevebauman committed May 6, 2024
2 parents 17e1dac + 843ffa4 commit 999f6ad
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 29 deletions.
54 changes: 46 additions & 8 deletions src/Models/Concerns/HasAttributes.php
Expand Up @@ -345,10 +345,23 @@ public function getDates(): array
// array key case so they are merged properly.
return array_merge(
array_change_key_case($this->defaultDates),
array_change_key_case($this->dates)
array_change_key_case($this->dates),
array_change_key_case($this->getDateCasts()),
);
}

/**
* Get the attributes casts that should be mutated to dates.
*/
protected function getDateCasts(): array
{
return array_map(function (string $cast) {
return explode(':', $cast, 2)[1];
}, array_filter($this->getCasts(), function ($cast) {
return $this->isDateTimeCast($cast);
}));
}

/**
* Convert the given date value to an LDAP compatible value.
*
Expand Down Expand Up @@ -488,20 +501,31 @@ protected function castRequiresArrayValue(string $key): bool
/**
* Cast the given attribute to JSON.
*/
protected function castAttributeAsJson(string $key, string $value): string
protected function castAttributeAsJson(string $key, mixed $value): string
{
$value = $this->asJson($value);

if ($value === false) {
$class = get_class($this);
$message = json_last_error_msg();

throw new Exception("Unable to encode attribute [{$key}] for model [{$class}] to JSON: {$message}.");
throw new Exception("Unable to encode attribute [$key] for model [$class] to JSON: $message.");
}

return $value;
}

/**
* Cast the given attribute to an LDAP primitive type.
*/
protected function castAttributeAsPrimitive(string $key, mixed $value): string
{
return match ($this->getCastType($key)) {
'bool', 'boolean' => $this->fromBoolean($value),
default => (string) $value,
};
}

/**
* Convert the model to its JSON representation.
*/
Expand Down Expand Up @@ -540,13 +564,23 @@ public function fromFloat(float $value): float
}

/**
* Cast the value to a boolean.
* Cast the value from an LDAP boolean string to a primitive boolean.
*/
protected function asBoolean(mixed $value): bool
{
$map = ['true' => true, 'false' => false];
return match (strtolower($value)) {
'true' => true,
'false' => false,
default => (bool) $value,
};
}

return $map[strtolower($value)] ?? (bool) $value;
/**
* Cast the value from a primitive boolean to an LDAP boolean string.
*/
protected function fromBoolean(bool $value): string
{
return $value ? 'TRUE' : 'FALSE';
}

/**
Expand Down Expand Up @@ -656,16 +690,20 @@ public function setAttribute(string $key, mixed $value): static

if ($this->hasSetMutator($key)) {
return $this->setMutatedAttributeValue($key, $value);
} elseif (
}

if (
$value &&
$this->isDateAttribute($key) &&
! $this->valueIsResetInteger($value)
) {
$value = $this->fromDateTime($value, $this->getDates()[$key]);
$value = (string) $this->fromDateTime($value, $this->getDates()[$key]);
}

if ($this->isJsonCastable($key) && ! is_null($value)) {
$value = $this->castAttributeAsJson($key, $value);
} elseif ($this->hasCast($key) && ! is_null($value)) {
$value = $this->castAttributeAsPrimitive($key, $value);
}

$this->attributes[$key] = Arr::wrap($value);
Expand Down
105 changes: 84 additions & 21 deletions tests/Unit/Models/ModelAttributeCastTest.php
Expand Up @@ -10,89 +10,125 @@ class ModelAttributeCastTest extends TestCase
{
public function test_boolean_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['boolAttribute' => ['TRUE']]);
$model = (new ModelCastStub)->setRawAttributes(['boolAttribute' => ['TRUE']]);
$this->assertTrue($model->boolAttribute);

$model = (new ModelCastStub())->setRawAttributes(['boolAttribute' => ['FALSE']]);
$model = (new ModelCastStub)->setRawAttributes(['boolAttribute' => ['FALSE']]);
$this->assertFalse($model->boolAttribute);

$model = (new ModelCastStub())->setRawAttributes(['booleanAttribute' => ['FALSE']]);
$model = (new ModelCastStub)->setRawAttributes(['booleanAttribute' => ['FALSE']]);
$this->assertFalse($model->booleanAttribute);

$model = (new ModelCastStub())->setRawAttributes(['booleanAttribute' => ['FALSE']]);
$model = (new ModelCastStub)->setRawAttributes(['booleanAttribute' => ['FALSE']]);
$this->assertFalse($model->booleanAttribute);

// Reverse casting

$model->booleanAttribute = true;
$this->assertTrue($model->booleanAttribute);
$this->assertEquals($model->getRawAttribute('booleanAttribute'), ['TRUE']);

// Casing differences

$model = (new ModelCastStub())->setRawAttributes(['boolAttribute' => ['true']]);
$model = (new ModelCastStub)->setRawAttributes(['boolAttribute' => ['true']]);
$this->assertTrue($model->boolAttribute);

$model = (new ModelCastStub())->setRawAttributes(['boolAttribute' => ['false']]);
$model = (new ModelCastStub)->setRawAttributes(['boolAttribute' => ['false']]);
$this->assertFalse($model->boolAttribute);

// Variable differences

$model = (new ModelCastStub())->setRawAttributes(['boolAttribute' => ['invalid']]);
$model = (new ModelCastStub)->setRawAttributes(['boolAttribute' => ['invalid']]);
$this->assertTrue($model->boolAttribute);

$model = (new ModelCastStub())->setRawAttributes(['boolAttribute' => ['']]);
$model = (new ModelCastStub)->setRawAttributes(['boolAttribute' => ['']]);
$this->assertFalse($model->boolAttribute);

$model = (new ModelCastStub())->setRawAttributes(['boolAttribute' => []]);
$model = (new ModelCastStub)->setRawAttributes(['boolAttribute' => []]);
$this->assertNull($model->boolAttribute);

$model = (new ModelCastStub());
$model = (new ModelCastStub);
$this->assertNull($model->boolAttribute);
}

public function test_float_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['floatAttribute' => ['12345.6789']]);
$model = (new ModelCastStub)->setRawAttributes(['floatAttribute' => ['12345.6789']]);

$this->assertIsFloat($value = $model->floatAttribute);
$this->assertEquals(12345.6789, $value);

// Reverse casting

$model->floatAttribute = 12345.6789;
$this->assertIsFloat($model->floatAttribute);
$this->assertEquals($model->getRawAttribute('floatAttribute'), ['12345.6789']);
}

public function test_double_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['doubleAttribute' => ['1234.567']]);
$model = (new ModelCastStub)->setRawAttributes(['doubleAttribute' => ['1234.567']]);

$this->assertIsFloat($value = $model->doubleAttribute);
$this->assertEquals(1234.567, $value);

// Reverse casting

$model->doubleAttribute = 1234.567;
$this->assertIsFloat($model->doubleAttribute);
$this->assertEquals($model->getRawAttribute('doubleAttribute'), ['1234.567']);
}

public function test_object_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['objectAttribute' => ['{"foo": 1, "bar": "two"}']]);
$model = (new ModelCastStub)->setRawAttributes(['objectAttribute' => ['{"foo": 1, "bar": "two"}']]);

$this->assertIsObject($value = $model->objectAttribute);

$object = (new \stdClass());
$object = (new \stdClass);
$object->foo = 1;
$object->bar = 'two';

$this->assertEquals($object, $value);

// Reverse casting

$model->objectAttribute = $object;
$this->assertIsObject($model->objectAttribute);
$this->assertEquals($model->getRawAttribute('objectAttribute'), ['{"foo":1,"bar":"two"}']);
}

public function test_json_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['jsonAttribute' => ['{"foo": 1, "bar": "two"}']]);
$model = (new ModelCastStub)->setRawAttributes(['jsonAttribute' => ['{"foo": 1, "bar": "two"}']]);

$this->assertEquals(['foo' => 1, 'bar' => 'two'], $model->jsonAttribute);

// Reverse casting

$model->jsonAttribute = ['foo' => 1, 'bar' => 'two'];
$this->assertEquals(['foo' => 1, 'bar' => 'two'], $model->jsonAttribute);
$this->assertEquals($model->getRawAttribute('jsonAttribute'), ['{"foo":1,"bar":"two"}']);
}

public function test_collection_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['collectionAttribute' => ['foo' => 1, 'bar' => 'two']]);
$model = (new ModelCastStub)->setRawAttributes(['collectionAttribute' => ['foo' => 1, 'bar' => 'two']]);

$this->assertInstanceOf(Collection::class, $collection = $model->collectionAttribute);

$this->assertEquals(['foo' => 1, 'bar' => 'two'], $collection->toArray());

// Reverse casting

$model->collectionAttribute = new Collection(['foo' => 1, 'bar' => 'two']);
$this->assertInstanceOf(Collection::class, $model->collectionAttribute);
$this->assertEquals($model->getRawAttribute('collectionAttribute'), ['{"foo":1,"bar":"two"}']);
}

public function test_integer_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes([
$model = (new ModelCastStub)->setRawAttributes([
'intAttribute' => ['1234.5678'],
'integerAttribute' => ['1234.5678'],
]);
Expand All @@ -102,36 +138,63 @@ public function test_integer_attributes_are_casted()

$this->assertEquals(1234, $model->intAttribute);
$this->assertEquals(1234, $model->integerAttribute);

// Reverse casting

$model->intAttribute = 1234;
$this->assertEquals(1234, $model->intAttribute);
$this->assertEquals($model->getRawAttribute('intAttribute'), ['1234']);
}

public function test_decimal_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['decimalAttribute' => ['1234.5678']]);
$model = (new ModelCastStub)->setRawAttributes(['decimalAttribute' => ['1234.5678']]);

$this->assertIsString($value = $model->decimalAttribute);

$this->assertEquals(1234.57, $value);

// Reverse casting

$model->decimalAttribute = 1234.57;
$this->assertIsString($model->decimalAttribute);
$this->assertEquals($model->getRawAttribute('decimalAttribute'), ['1234.57']);
}

public function test_ldap_datetime_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['ldapDateTime' => ['20201002021244Z']]);
$model = (new ModelCastStub)->setRawAttributes(['ldapDateTime' => ['20201002021244Z']]);

$this->assertEquals('Fri Oct 02 2020 02:12:44 GMT+0000', $model->ldapDateTime->toString());

// Reverse casting

$model->ldapDateTime = new \DateTime('2020-10-02 02:12:44');
$this->assertEquals($model->getRawAttribute('ldapDateTime'), ['20201002021244Z']);
}

public function test_windows_datetime_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['windowsDateTime' => ['20201002021618.0Z']]);
$model = (new ModelCastStub)->setRawAttributes(['windowsDateTime' => ['20201002021618.0Z']]);

$this->assertEquals('Fri Oct 02 2020 02:16:18 GMT+0000', $model->windowsDateTime->toString());

// Reverse casting

$model->windowsDateTime = new \DateTime('2020-10-02 02:16:18');
$this->assertEquals($model->getRawAttribute('windowsDateTime'), ['20201002021618.0Z']);
}

public function test_windows_int_datetime_attributes_are_casted()
{
$model = (new ModelCastStub())->setRawAttributes(['windowsIntDateTime' => ['132460789290000000']]);
$model = (new ModelCastStub)->setRawAttributes(['windowsIntDateTime' => ['132460789290000000']]);

$this->assertEquals('Fri Oct 02 2020 02:22:09 GMT+0000', $model->windowsIntDateTime->toString());

// Reverse casting

$model->windowsIntDateTime = new \DateTime('2020-10-02 02:22:09');
$this->assertEquals($model->getRawAttribute('windowsIntDateTime'), ['132460789290000000']);
}
}

Expand Down

0 comments on commit 999f6ad

Please sign in to comment.