+ base hydrator implementation added

This commit is contained in:
2020-11-28 01:54:00 +02:00
parent f43dac8374
commit 354b8a26ad
7 changed files with 2731 additions and 3 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
/.idea
/.idea
/vendor

View File

@@ -3,7 +3,7 @@
"type": "library",
"description": "",
"keywords": [],
"homepage": "",
"homepage": "https://vague.tech",
"authors": [
{
"name": "Dan A. Haman",
@@ -22,5 +22,8 @@
}
},
"scripts": {
},
"require-dev": {
"phpunit/phpunit": "^9.4"
}
}
}

1970
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
<?php
namespace Vague\Hyy;
/**
* Interface HydratableInterface
* @package Vague\Hyy
*/
interface HydratableInterface
{
/**
* @return array
*/
public function toArray(): array;
/**
* @param array $rawData
* @return HydratableInterface
*/
public function fromArray(array $rawData): HydratableInterface;
}

104
src/Hyydrator.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
namespace Vague\Hyy;
/**
* Class Hyydrator
* @package Vague\Hyy
*/
class Hyydrator
{
const REGEX_FIELD_NAME = '/([^_]+)_(.+)/i';
const CONFIG_RELATION_TYPE = 'type';
const CONFIG_RELATION_CLASS = 'class';
const TYPE_MULTIPLE = 0;
const TYPE_SINGLE = 1;
const GETTERS = [
self::TYPE_MULTIPLE => 'get%ss',
self::TYPE_SINGLE => 'get%s',
];
const SETTERS = [
self::TYPE_MULTIPLE => 'set%ss',
self::TYPE_SINGLE => 'set%s',
];
/**
* @param array $rawData
* @param HydratableInterface $base
* @param array $configuration
* @return array
*/
public function hydrate(array $rawData, HydratableInterface $base, array $configuration): array
{
$baseClass = get_class($base);
$result = [];
foreach ($rawData as $row) {
$nativeRow = [];
$relationsRow = [];
foreach ($row as $key => $value) {
if (preg_match(self::REGEX_FIELD_NAME, $key, $m)
&& array_key_exists($m[1], $configuration)
) {
$relationsRow[$m[1]][$m[2]] = $value;
continue;
}
$nativeRow[$key] = $value;
}
$hash = md5(implode('', $nativeRow));
if (!array_key_exists($hash, $result)) {
$result[$hash] = (new $baseClass)->fromArray($nativeRow);
}
$base = $result[$hash];
foreach ($relationsRow as $key => $value) {
if ($this->checkEmpty($value)) {
continue;
}
$hash = md5($key . implode('', $value));
if (!is_array($configuration[$key])) {
$configuration[$key] = [
self::CONFIG_RELATION_TYPE => self::TYPE_MULTIPLE,
self::CONFIG_RELATION_CLASS => $configuration[$key],
];
}
$setMethodName = sprintf(self::SETTERS[$configuration[$key][self::CONFIG_RELATION_TYPE]], ucfirst($key));
$getMethodName = sprintf(self::GETTERS[$configuration[$key][self::CONFIG_RELATION_TYPE]], ucfirst($key));
$relationClass = $configuration[$key][self::CONFIG_RELATION_CLASS];
if ($configuration[$key][self::CONFIG_RELATION_TYPE] === self::TYPE_MULTIPLE) {
$existing = call_user_func([$base, $getMethodName]);
if (!array_key_exists($hash, $existing)) {
$base->$setMethodName(array_merge($existing, [$hash => (new $relationClass)->fromArray($value),]));
}
}
if ($configuration[$key][self::CONFIG_RELATION_TYPE] === self::TYPE_SINGLE) {
$base->$setMethodName((new $relationClass)->fromArray($value));
}
}
}
return $result;
}
/**
* @param array $data
* @return bool
*/
private function checkEmpty(array $data): bool
{
foreach ($data as $item) {
if ($item !== null) {
return false;
}
}
return true;
}
}

67
tests/BaseTest.php Normal file
View File

@@ -0,0 +1,67 @@
<?php
namespace Vague\Hyy\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use ReflectionException;
use ReflectionProperty;
/**
* Class Test
* @package VagueSoftware\CakeTravel\Tests
*/
abstract class BaseTest extends TestCase
{
const VALUE_ID = 1;
const VALUE_LOCALE_ID = '1';
/**
* @param $class
* @param $property
* @return ReflectionProperty
* @throws ReflectionException
*/
protected function getPrivateProperty($class, $property)
{
$class = is_string($class) ? $class : get_class($class);
$reflector = new \ReflectionClass($class);
if (!$reflector->hasProperty($property) &&
$reflector->getParentClass() &&
$reflector->getParentClass()->hasProperty($property)
) {
$reflector = $reflector->getParentClass();
}
$property = $reflector->getProperty($property);
$property->setAccessible(true);
return $property;
}
/**
* @param $class
* @param $property
* @param $value
* @return $this
* @throws ReflectionException
*/
protected function setPrivateProperty($class, $property, $value)
{
$property = $this->getPrivateProperty($class, $property);
$property->setValue($class, $value);
return $this;
}
/**
* @param $className
* @param array $methods
* @return MockObject
*/
protected function getClassMock($className, array $methods = [])
{
return $this->getMockBuilder($className)
->disableOriginalConstructor()
->setMethods($methods)
->getMock();
}
}

562
tests/HyydratorTest.php Normal file
View File

@@ -0,0 +1,562 @@
<?php
namespace Vague\Hyy\Test;
use Vague\Hyy\HydratableInterface;
use Vague\Hyy\Hyydrator;
/**
* Class HyydratorTest
* @package Vague\Hyy\Test
*/
class HyydratorTest extends BaseTest
{
/**
* @param array $data
* @param HydratableInterface $hydratable
* @param array $configuration
* @param array $expected
* @dataProvider hydrateDataProvider
*/
public function testHydrate(array $data, HydratableInterface $hydratable, array $configuration, array $expected)
{
$testObject = new Hyydrator();
$result = $testObject->hydrate($data, $hydratable, $configuration);
$this->assertEquals($expected, $result);
}
/**
* @return array
*/
public function hydrateDataProvider(): array
{
return [
[
[
[
'id' => 1,
'name' => 'test',
'relation_id' => 1,
'relation_name' => 'aaa',
],
[
'id' => 1,
'name' => 'test',
'relation_id' => 2,
'relation_name' => 'bbb',
],
],
new Data(),
[
'relation' => Relation::class,
],
[
'ba88c155ba898fc8b5099893036ef205' => (new Data())->setId(1)->setName('test')
->setRelations([
'da8c84d77240a67982260c526ca537ff' => (new Relation())->setId(1)->setName('aaa'),
'eb086fd29624e3ba84441d7fe86b7980' => (new Relation())->setId(2)->setName('bbb'),
]),
],
],
[
[
[
'id' => 1,
'name' => 'test',
'relation_id' => 1,
'relation_name' => 'aaa',
'test_id' => 2,
'test_name' => 'ccc'
],
[
'id' => 1,
'name' => 'test',
'relation_id' => 2,
'relation_name' => 'bbb',
],
],
new Data(),
[
'relation' => Relation::class,
'test' => Relation::class,
],
[
'ba88c155ba898fc8b5099893036ef205' => (new Data())->setId(1)->setName('test')
->setRelations([
'da8c84d77240a67982260c526ca537ff' => (new Relation())->setId(1)->setName('aaa'),
'eb086fd29624e3ba84441d7fe86b7980' => (new Relation())->setId(2)->setName('bbb'),
])
->setTests([
'f18837e7d4c8f146ff9e5e79ed59402d' => (new Relation())->setId(2)->setName('ccc'),
]),
],
],
[
[
[
'id' => 1,
'name' => 'test',
'relation_id' => 1,
'relation_name' => 'aaa',
'test_id' => 2,
'test_name' => 'ccc'
],
[
'id' => 1,
'name' => 'test',
'relation_id' => 2,
'relation_name' => 'bbb',
],
[
'id' => 2,
'name' => 'qwerty',
'relation_id' => 1,
'relation_name' => 'aaa',
'test_id' => 2,
'test_name' => 'ccc'
],
[
'id' => 2,
'name' => 'qwerty',
'relation_id' => 1,
'relation_name' => 'aaa',
'test_id' => 3,
'test_name' => 'ddd'
],
],
new Data(),
[
'relation' => Relation::class,
'test' => Relation::class,
],
[
'ba88c155ba898fc8b5099893036ef205' => (new Data())->setId(1)->setName('test')
->setRelations([
'da8c84d77240a67982260c526ca537ff' => (new Relation())->setId(1)->setName('aaa'),
'eb086fd29624e3ba84441d7fe86b7980' => (new Relation())->setId(2)->setName('bbb'),
])
->setTests([
'f18837e7d4c8f146ff9e5e79ed59402d' => (new Relation())->setId(2)->setName('ccc'),
]),
'11861fca003eac39d43d9671e4cf0905' => (new Data())->setId(2)->setName('qwerty')
->setRelations([
'da8c84d77240a67982260c526ca537ff' => (new Relation())->setId(1)->setName('aaa'),
])
->setTests([
'f18837e7d4c8f146ff9e5e79ed59402d' => (new Relation())->setId(2)->setName('ccc'),
'79b6701e0246cbb9b1fa3aae9b9de670' => (new Relation())->setId(3)->setName('ddd'),
]),
],
],
[
[
[
'id' => 1,
'name' => 'test',
'relation_id' => null,
'relation_name' => null,
],
],
new Data(),
[
'relation' => Relation::class,
],
[
'ba88c155ba898fc8b5099893036ef205' => (new Data())->setId(1)->setName('test'),
],
],
[
[
[
'id' => 1,
'name' => 'test',
'extra_data' => 'something',
'relation_id' => 1,
'relation_name' => 'aaa',
],
[
'id' => 1,
'name' => 'test',
'extra_data' => 'something',
'relation_id' => 2,
'relation_name' => 'bbb',
],
],
new ExtraData(),
[
'relation' => Relation::class,
],
[
'a9d879233bb48af51548f6584cee918b' => (new ExtraData())->setExtraData('something')
->setId(1)
->setName('test')
->setRelations([
'da8c84d77240a67982260c526ca537ff' => (new Relation())->setId(1)->setName('aaa'),
'eb086fd29624e3ba84441d7fe86b7980' => (new Relation())->setId(2)->setName('bbb'),
]),
],
],
[
[
[
'id' => 12,
'relation_id' => 13,
'relation_name' => 'axa',
],
],
new SingleRelation(),
[
'relation' => [
'type' => 1,
'class' => Relation::class,
],
],
[
'c20ad4d76fe97759aa27a0c99bff6710' => (new SingleRelation())->setId(12)
->setRelation((new Relation())->setId(13)->setName('axa'))
]
],
[
[
[
'id' => 11,
'relation_id' => 14,
'relation_name' => 'axa',
'extra_id' => 8,
'extra_name' => 'asd',
'extra_extra_data' => ';df',
],
],
new SingleRelation(),
[
'relation' => [
'type' => 1,
'class' => Relation::class,
],
'extra' => ExtraData::class,
],
[
'6512bd43d9caa6e02c990b0a82652dca' => (new SingleRelation())->setId(11)
->setRelation((new Relation())->setId(14)->setName('axa'))
->setExtras([
'5f1974e02161a805332685ebd811d13e' => (new ExtraData())->setExtraData(';df')->setId(8)->setName('asd'),
])
]
],
];
}
}
class Data implements HydratableInterface
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $name;
/**
* @var array|Relation[]
*/
private $relations = [];
/**
* @var array|Relation[]
*/
private $tests = [];
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @param int $id
* @return Data
*/
public function setId(int $id): Data
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
* @return Data
*/
public function setName(string $name): Data
{
$this->name = $name;
return $this;
}
/**
* @return array|Relation[]
*/
public function getRelations()
{
return $this->relations;
}
/**
* @param array|Relation[] $relations
* @return Data
*/
public function setRelations($relations)
{
$this->relations = $relations;
return $this;
}
/**
* @return array|Relation[]
*/
public function getTests()
{
return $this->tests;
}
/**
* @param array|Relation[] $tests
* @return Data
*/
public function setTests($tests)
{
$this->tests = $tests;
return $this;
}
/**
* @return array
*/
public function toArray(): array
{
return [];
}
/**
* @param array $rawData
* @return HydratableInterface
*/
public function fromArray(array $rawData): HydratableInterface
{
$this->id = $rawData['id'];
$this->name = $rawData['name'];
return $this;
}
}
class Relation implements HydratableInterface
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $name;
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @param int $id
* @return Relation
*/
public function setId(int $id): Relation
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
* @return Relation
*/
public function setName(string $name): Relation
{
$this->name = $name;
return $this;
}
/**
* @return array
*/
public function toArray(): array
{
return [];
}
/**
* @param array $rawData
* @return HydratableInterface
*/
public function fromArray(array $rawData): HydratableInterface
{
$this->id = $rawData['id'];
$this->name = $rawData['name'];
return $this;
}
}
class SingleRelation implements HydratableInterface
{
/**
* @var int
*/
private $id;
/**
* @var Relation
*/
private $relation;
/**
* @var array
*/
private $extra = [];
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @param int $id
* @return SingleRelation
*/
public function setId(int $id): SingleRelation
{
$this->id = $id;
return $this;
}
/**
* @return Relation
*/
public function getRelation(): Relation
{
return $this->relation;
}
/**
* @param Relation $relation
* @return SingleRelation
*/
public function setRelation(Relation $relation): SingleRelation
{
$this->relation = $relation;
return $this;
}
/**
* @return array
*/
public function getExtras(): array
{
return $this->extra;
}
/**
* @param array $extra
* @return SingleRelation
*/
public function setExtras(array $extra): SingleRelation
{
$this->extra = $extra;
return $this;
}
/**
* @return array
*/
public function toArray(): array
{
return [];
}
/**
* @param array $rawData
* @return HydratableInterface
*/
public function fromArray(array $rawData): HydratableInterface
{
$this->id = $rawData['id'];
return $this;
}
}
class ExtraData extends Data
{
/**
* @var string
*/
private $extraData;
/**
* @return string
*/
public function getExtraData(): string
{
return $this->extraData;
}
/**
* @param string $extraData
* @return ExtraData
*/
public function setExtraData(string $extraData): ExtraData
{
$this->extraData = $extraData;
return $this;
}
/**
* @param array $rawData
* @return HydratableInterface
*/
public function fromArray(array $rawData): HydratableInterface
{
parent::fromArray($rawData);
$this->extraData = $rawData['extra_data'];
return $this;
}
}