Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/DataCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Jasny\MetaCast;

use function Jasny\expect_type;

/**
* Perform type casting
*/
class DataCast
{
/**
* Type cast handlers for class properties
* @var array
**/
protected $handlers;

/**
* Create class instance
*
* @param array $handlers
*/
public function __construct(array $handlers)
{
$this->handlers = $handlers;
}

/**
* Cast data using handlers
*
* @param array|object $data
* @return array|object
*/
public function cast($data)
{
expect_type($data, ['array', 'object']);

if ($isArray = is_array($data)) {
$data = (object)$data;
}

$data = clone $data;

foreach ($this->handlers as $name => $handler) {
if (isset($data->$name)) {
$data->$name = $handler->cast($data->$name);
}
}

return $isArray ? (array)$data : $data;
}
}
67 changes: 45 additions & 22 deletions src/MetaCast.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
use Jasny\Meta\MetaClass;
use Jasny\TypeCastInterface;
use \InvalidArgumentException;
use function Jasny\expect_type;

/**
* Cast data to class
* Cast data to class using class metadata
*/
class MetaCast
{
Expand Down Expand Up @@ -38,55 +39,77 @@ public function __construct(FactoryInterface $metaFactory, TypeCastInterface $ty
$this->typeCast = $typeCast;
}

/**
* Use object as callable
*
* @param string|object $class
* @param array|object $data
* @return array|object
*/
final public function __invoke($class, $data)
{
return $this->cast($class, $data);
}

/**
* Cast data to given class
*
* @param string $class
* @param string|object $class
* @param array|object $data
* @return object
* @return array|object
*/
public function cast(string $class, $data)
public function cast($class, $data)
{
if (!is_array($data) && !is_object($data)) {
$type = gettype($data);
throw new InvalidArgumentException("Can not cast '$type' to '$class': expected object or array");
}
expect_type($class, ['string', 'object']);
expect_type($data, ['array', 'object']);

if (is_array($data)) {
$data = (object)$data;
if (is_object($class)) {
$class = get_class($class);
}

if (is_a($data, $class)) {
return clone $data;
}

$meta = $this->metaFactory->forClass($class);
$data = $this->castProperties($meta, $data);
$handlers = $this->getHandlers($meta, $data);
$caster = $this->getDataCaster($handlers);

return $this->typeCast->to($class)->cast($data);
return $caster->cast($data);
}

/**
* Cast class properties
* Get cast handlers
*
* @param MetaClass $meta
* @param object $data
* @return object
* @param array|object $data
* @return array
*/
protected function castProperties(MetaClass $meta, $data)
protected function getHandlers(MetaClass $meta, $data): array
{
$data = clone $data;
$handlers = [];
$properties = $meta->getProperties();

foreach ($properties as $name => $item) {
$toType = $item->get('type');
if (!$toType || !isset($data->$name)) {
continue;
}

$data->$name = $this->typeCast->to($toType)->cast($data->$name);
if ($toType) {
$handlers[$name] = $this->typeCast->to($toType);
}
}

return $data;
return $handlers;
}

/**
* Get instance of data caster
*
* @codeCoverageIgnore
* @param array $handlers
* @return DataCast
*/
protected function getDataCaster(array $handlers): DataCast
{
return new DataCast($handlers);
}
}
93 changes: 93 additions & 0 deletions tests/DataCastTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace Jasny\MetaCast\Tests;

use Jasny\Meta\FactoryInterface;
use Jasny\Meta\MetaClass;
use Jasny\Meta\MetaProperty;
use Jasny\MetaCast\MetaCast;
use Jasny\MetaCast\DataCast;
use Jasny\TypeCastInterface;
use Jasny\TypeCast\HandlerInterface;
use PHPUnit\Framework\TestCase;
use stdClass;

/**
* @covers Jasny\MetaCast\DataCast
*/
class DataCastTest extends TestCase
{
/**
* Provide data for testing 'cast' method
*
* @return array
*/
public function castProvider()
{
$data = [
'foo' => 'foo_value',
'baz' => 'baz_value'
];

$expected = [
'foo' => 'foo_casted',
'baz' => 'baz_casted'
];

return [
[$data, $expected],
[(object)$data, (object)$expected]
];
}

/**
* Test 'cast' method
*
* @dataProvider castProvider
*/
public function testCast($data, $expected)
{
$handlers = [
'foo' => $this->createMock(HandlerInterface::class),
'bar' => $this->createMock(HandlerInterface::class),
'baz' => $this->createMock(HandlerInterface::class),
];

$handlers['foo']->expects($this->once())->method('cast')->with('foo_value')->willReturn('foo_casted');
$handlers['baz']->expects($this->once())->method('cast')->with('baz_value')->willReturn('baz_casted');

$dataCast = new DataCast($handlers);
$result = $dataCast->cast($data);

$this->assertEquals($expected, $result);
}

/**
* Provide data for testing 'cast' method for promitive values
*
* @return array
*/
public function castPrimitiveProvider()
{
return [
[12, 'integer'],
['foo', 'string'],
[true, 'boolean'],
[null, 'NULL'],
];
}

/**
* Test 'cast' method for primitive value
*
* @dataProvider castPrimitiveProvider
*/
public function testCastPrimitive($data, $type)
{
$this->expectException(\TypeError::class);
$this->expectExceptionMessage("Expected array or object, $type given");

$dataCast = new DataCast([]);
$dataCast->cast($data);
}
}
Loading