Skip to content

Commit

Permalink
add additional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
frasmage committed Apr 29, 2024
1 parent 34c2668 commit 9f3dcd6
Show file tree
Hide file tree
Showing 8 changed files with 461 additions and 85 deletions.
13 changes: 12 additions & 1 deletion docs/source/handling_meta.rst
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,18 @@ You can enforce that any meta attached to a particular key is always of a partic
'children' => 'collection:\App\ExampleMetable',
];

//...
// equivalent to:
protected function metaCasts(): array
{
return [
'optin' => 'boolean',
'age' => 'integer',
'secret' => 'encrypted:string',
'parent' => ExampleMetable::class,
'children' => 'collection:\App\ExampleMetable',
];
}

}

All `cast types supported by Eloquent<https://laravel.com/docs/11.x/eloquent-mutators#attribute-casting>`_ are supported, with the following modifications:
Expand Down
121 changes: 82 additions & 39 deletions src/Metable.php
Original file line number Diff line number Diff line change
Expand Up @@ -844,58 +844,22 @@ protected function castMetaValueIfNeeded(string $key, mixed $value): mixed
protected function castMetaValue(string $key, mixed $value, string $cast): mixed
{
if ($cast == 'array' || $cast == 'object') {
$assoc = $cast == 'array';
if (is_string($value)) {
$value = json_decode($value, $assoc, 512, JSON_THROW_ON_ERROR);
}
return json_decode(
json_encode($value, JSON_THROW_ON_ERROR),
$assoc,
512,
JSON_THROW_ON_ERROR
);
return $this->castMetaToJson($cast, $value);
}

if ($cast == 'hashed') {
return $this->castAttributeAsHashedString($key, $value);
}

if ($cast == 'collection' || str_starts_with($cast, 'collection:')) {
if ($value instanceof \Illuminate\Support\Collection) {
$collection = $value;
} elseif ($value instanceof Model) {
$collection = $value->newCollection([$value]);
} else {
$collection = collect($value);
}

if (str_starts_with($cast, 'collection:')) {
$class = substr($cast, 11);
$collection->each(function ($item) use ($class): void {
if (!$item instanceof $class) {
throw CastException::invalidClassCast($class, $item);
}
});
}

return $collection;
return $this->castMetaToCollection($cast, $value);
}

if (class_exists($cast)
&& !is_a($cast, Castable::class, true)
&& $cast != 'datetime'
) {
if ($value instanceof $cast) {
return $value;
}

if (is_a($cast, Model::class, true)
&& (is_string($value) || is_int($value))
) {
return $cast::find($value);
}

throw CastException::invalidClassCast($cast, $value);
return $this->castMetaToClass($value, $cast);
}

// leverage Eloquent built-in casting functionality
Expand Down Expand Up @@ -1005,4 +969,83 @@ abstract public function load($relations);
abstract public function relationLoaded($key);

abstract protected function castAttributeAsHashedString($key, $value);

/**
* @param mixed $value
* @param string $cast
* @return Collection|\Illuminate\Support\Collection
*/
protected function castMetaToCollection(string $cast, mixed $value): \Illuminate\Support\Collection
{
if ($value instanceof \Illuminate\Support\Collection) {
$collection = $value;
} elseif ($value instanceof Model) {
$collection = $value->newCollection([$value]);
} elseif (is_iterable($value)) {
$isEloquentModels = true;
$notEmpty = false;

foreach ($value as $item) {
$notEmpty = true;
if (!$item instanceof Model) {
$isEloquentModels = false;
break;
}
}
$collection = $isEloquentModels && $notEmpty
? $value[0]->newCollection($value)
: collect($value);
}

if (str_starts_with($cast, 'collection:')) {
$class = substr($cast, 11);
$collection->each(function ($item) use ($class): void {
if (!$item instanceof $class) {
throw CastException::invalidClassCast($class, $item);
}
});
}

return $collection;
}

/**
* @param string $cast
* @param mixed $value
* @return mixed
* @throws \JsonException
*/
protected function castMetaToJson(string $cast, mixed $value): mixed
{
$assoc = $cast == 'array';
if (is_string($value)) {
$value = json_decode($value, $assoc, 512, JSON_THROW_ON_ERROR);
}
return json_decode(
json_encode($value, JSON_THROW_ON_ERROR),
$assoc,
512,
JSON_THROW_ON_ERROR
);
}

/**
* @param mixed $value
* @param string $cast
* @return mixed
*/
protected function castMetaToClass(mixed $value, string $cast): mixed
{
if ($value instanceof $cast) {
return $value;
}

if (is_a($cast, Model::class, true)
&& (is_string($value) || is_int($value))
) {
return $cast::findOrFail($value);
}

throw CastException::invalidClassCast($cast, $value);
}
}
2 changes: 1 addition & 1 deletion src/MetableAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function offsetExists($key): bool
return $this->hasMeta($this->metaAttributeToKey($key));
}

return parent::isset($key);
return parent::offsetExists($key);
}

public function offsetUnset($key): void
Expand Down
34 changes: 34 additions & 0 deletions tests/Integration/DataType/EnumHandlerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Plank\Metable\Tests\Integration\DataType;

use Plank\Metable\DataType\BackedEnumHandler;
use Plank\Metable\DataType\PureEnumHandler;
use Plank\Metable\Tests\TestCase;

class EnumHandlerTest extends TestCase
{
public function test_back_enum_handles_unknown_class()
{
$handler = new BackedEnumHandler();
$this->assertNull($handler->unserializeValue('baz#value'));
}

public function test_back_enum_handles_non_enum()
{
$handler = new BackedEnumHandler();
$this->assertNull($handler->unserializeValue('stdClass#value'));
}

public function test_pure_enum_handles_unknown_class()
{
$handler = new PureEnumHandler();
$this->assertNull($handler->unserializeValue('baz#value'));
}

public function test_pure_enum_handles_non_enum()
{
$handler = new PureEnumHandler();
$this->assertNull($handler->unserializeValue('stdClass#value'));
}
}
7 changes: 7 additions & 0 deletions tests/Integration/MetaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ public function test_it_can_encrypt_its_value(): void
$this->assertEquals($hmac, $meta->hmac);
$this->assertEquals('encrypted:string', $meta->type);
$this->assertNull($meta->numeric_value);


$rawValue = $meta->getRawValue();
// should not re-encrypt
$meta->encrypt();
$this->assertEquals($rawValue, $meta->getRawValue());
$this->assertEquals('encrypted:string', $meta->type);
}

private function makeMeta(array $attributes = []): Meta
Expand Down
18 changes: 14 additions & 4 deletions tests/Integration/MetableAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public function test_it_doesnt_overwrite_existing_attributes()

$model->setAttribute('meta_attribute', 'baz');
$this->assertFalse($model->hasMeta('attribute'));

$model->meta_attribute = 'qux';
$this->assertTrue($model->offsetExists('meta_attribute'));
$model->offsetUnset('meta_attribute');
$this->assertNull($model->meta_attribute);
}

public function test_it_converts_to_array()
Expand All @@ -72,21 +77,26 @@ public function test_it_converts_to_array()

$model->makeHidden('meta_var2', 'created_at', 'updated_at', 'meta');

$array = $model->toArray();
$this->assertEquals([
'meta_attribute' => '',
'id' => $model->getKey(),
'meta_foo' => 'bar',
'meta_var' => 'foo'
], $array);
], $model->toArray());

$model->includeMetaInArray = false;
$this->assertEquals([
'meta_attribute' => '',
'id' => $model->getKey(),
], $model->toArray());

$model->includeMetaInArray = true;
$model->makeMetaHidden();

$array = $model->toArray();
$this->assertEquals([
'meta_attribute' => '',
'id' => $model->getKey(),
], $array);
], $model->toArray());
}

private function createMetable(array $attributes = []): SampleMetable
Expand Down
Loading

0 comments on commit 9f3dcd6

Please sign in to comment.