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 d8510aa
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 47 deletions.
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);
}
}
41 changes: 34 additions & 7 deletions tests/Integration/MetableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Stringable;
use Plank\Metable\Exceptions\CastException;
use Plank\Metable\Meta;
use Plank\Metable\Tests\Mocks\SampleMetable;
use Plank\Metable\Tests\Mocks\SampleMetableSoftDeletes;
use Plank\Metable\Tests\Mocks\SampleSerializable;
use Plank\Metable\Tests\TestCase;
use ReflectionClass;

Expand Down Expand Up @@ -880,6 +883,10 @@ public static function castProvider(): array
'collection - eloquent' => ['collection', $model, fn ($result) => $result->modelKeys() === $modelCollection->modelKeys(), 'collection', false],
'collection - eloquent collection' => ['collection', $modelCollection, fn ($result) => $result->modelKeys() === $modelCollection->modelKeys(), 'collection', false],
'collection - null' => ['collection', null, null, 'null'],
'collection:class - eloquent object' => ['collection:'.SampleMetable::class, $model, fn ($result) => $result->modelKeys() === $modelCollection->modelKeys(), 'collection', false],
'collection:class - eloquent array' => ['collection:'.SampleMetable::class, [$model], fn ($result) => $result->modelKeys() === $modelCollection->modelKeys(), 'collection', false],
'collection:class - eloquent collection' => ['collection:'.SampleMetable::class, $modelCollection, fn ($result) => $result->modelKeys() === $modelCollection->modelKeys(), 'collection', false],
'collection:class - object collection' => ['collection:'.\stdClass::class, collect([$object]), collect([$object]), 'serialized', false],
'stringable - string' => [AsStringable::class, 'foo', new Stringable('foo'), 'stringable', false],
'stringable - int' => [AsStringable::class, 123, new Stringable('123'), 'stringable', false],
'stringable - null' => [AsStringable::class, null, null, 'null'],
Expand All @@ -903,7 +910,10 @@ public function test_it_casts_meta_values(
): void {
$this->useDatabase();

if ($cast === 'collection' || $cast === 'encrypted:collection') {
if ($cast === 'collection'
|| $cast === 'encrypted:collection'
|| str_starts_with($cast, 'collection:')
) {
$model = new SampleMetable();
$model->id = 99;
$model->save();
Expand All @@ -923,15 +933,32 @@ public function test_it_casts_meta_values(
$this->assertSame($expectedHandlerType, $metable->getMetaRecord($key)->type);
}

public function test_it_can_cast_meta_values(): void
public static function invalidClassCastProvider(): array
{
return [
'collection:class - string' => ['collection:stdClass', collect('bar')],
'collection:class - int' => ['collection:stdClass', collect(123)],
'collection:class - other class' => ['collection:stdClass', collect(new SampleSerializable([]))],
'class - string' => [\stdClass::class, 'bar'],
'class - int' => [\stdClass::class, 123],
'class - other class' => [\stdClass::class, new SampleSerializable([])],
'eloquent - int' => [SampleMetable::class, 999, ModelNotFoundException::class],
'eloquent - string' => [SampleMetable::class, 'abc', ModelNotFoundException::class],
'eloquent - other class' => [SampleMetable::class, new \stdClass()],
];
}

/** @dataProvider invalidClassCastProvider */
public function test_it_throws_for_invalid_class_cast(
string $cast,
mixed $invalidValue,
string $expectedException = CastException::class
): void {
$this->expectException($expectedException);
$this->useDatabase();
$metable = $this->createMetable();
$metable->setMeta('castable', 123);

$result = SampleMetable::first();

$this->assertSame('123', $result->getMeta('castable'));
$metable->mergeMetaCasts(['foo' => $cast]);
$metable->setMeta('foo', $invalidValue);
}

private function makeMeta(array $attributes = []): Meta
Expand Down
7 changes: 6 additions & 1 deletion tests/Mocks/SampleMetable.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ class SampleMetable extends Model implements MetableInterface
];

protected $metaCasts = [
'castable' => 'string',
'castable' => 'int',
];

public function metaCasts(): array
{
return ['castable' => 'string'];
}
}

0 comments on commit d8510aa

Please sign in to comment.