diff --git a/src/Ting/Repository/HydratorValueObject.php b/src/Ting/Repository/HydratorValueObject.php new file mode 100644 index 00000000..40145b5d --- /dev/null +++ b/src/Ting/Repository/HydratorValueObject.php @@ -0,0 +1,165 @@ + + */ +class HydratorValueObject implements HydratorInterface +{ + /** + * @var class-string + */ + protected $objectToHydrate; + /** + * @var ResultInterface + */ + protected $result = null; + /** + * @var HydratableMetadataRepository + */ + private $hydrateableMetadataRepository; + + /** + * @param class-string $objectToHydrate + */ + public function __construct(string $objectToHydrate) + { + $this->objectToHydrate = $objectToHydrate; + } + + /** + * @param MetadataRepository $metadataRepository + * @return void + */ + public function setMetadataRepository(MetadataRepository $metadataRepository) + { + // Useless for this hydrator + } + + /** + * @param UnitOfWork $unitOfWork + * @return void + */ + public function setUnitOfWork(UnitOfWork $unitOfWork) + { + // Useless for this hydrator + } + + /** + * @param ResultInterface $result + * @return $this + */ + public function setResult(ResultInterface $result) + { + $this->result = $result; + return $this; + } + + /** + * @return \Generator + */ + #[\ReturnTypeWillChange] + public function getIterator() + { + if (method_exists($this->objectToHydrate, '__construct')) { + foreach ($this->result as $key => $row) { + yield $key => new $this->objectToHydrate(...array_combine(array_column($row, 'name'), array_column($row, 'value'))); + } + } else { + + $methods = []; + foreach ($this->result as $key => $row) { + foreach ($row as $column) { + $setter = 'set'.str_replace("_", "", ucwords($column['name'], " /_")); // snake case by default in database + if (method_exists($this->objectToHydrate, $setter)) { + $methods[$column['name']] = $setter; + } else { + throw new Exception('There is no setter for column "'.$column['name'].'"'); + } + } + break; + } + + $this->result->rewind(); + + foreach ($this->result as $key => $row) { + yield $key => $this->hydrateObject($row, $methods); + } + } + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + if ($this->result === null) { + return 0; + } + + return $this->result->getNumRows(); + } + + /** + * @throws Exception + */ + private function hydrateObject(array $row, $methods) + { + $object = new $this->objectToHydrate(); + foreach ($row as $column) { + $setter = $methods[$column['name']]; + $object->$setter($column['value']); + } + + return $object; + } + + private function getSerializerFromType(string $type) + { + + } + + /** + * @param HydratableMetadataRepository $metadataRepository + * @return void + */ + public function setHydratableMetadataRepository(HydratableMetadataRepository $metadataRepository) + { + + } +} diff --git a/tests/fixtures/ValueObject/Bouh.php b/tests/fixtures/ValueObject/Bouh.php new file mode 100644 index 00000000..4fa300ef --- /dev/null +++ b/tests/fixtures/ValueObject/Bouh.php @@ -0,0 +1,36 @@ +firstname; + } + + public function setFirstname($firstname) + { + $this->firstname = $firstname; + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } +} diff --git a/tests/fixtures/ValueObject/BouhWithConstruct.php b/tests/fixtures/ValueObject/BouhWithConstruct.php new file mode 100644 index 00000000..146c31bb --- /dev/null +++ b/tests/fixtures/ValueObject/BouhWithConstruct.php @@ -0,0 +1,32 @@ +firstname = $firstname; + $this->name = $name; + } + + /** + * @return mixed + */ + public function getFirstname() + { + return $this->firstname; + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } +} diff --git a/tests/fixtures/ValueObject/BouhWithNativeType.php b/tests/fixtures/ValueObject/BouhWithNativeType.php new file mode 100644 index 00000000..b29c5b02 --- /dev/null +++ b/tests/fixtures/ValueObject/BouhWithNativeType.php @@ -0,0 +1,78 @@ +nb; + } + + public function setNb(int $nb): void + { + $this->nb = $nb; + } + + public function getAvg(): float + { + return $this->avg; + } + + public function setAvg(float $avg): void + { + $this->avg = $avg; + } + + public function getMyName(): string + { + return $this->myName; + } + + public function setMyName(string $myName): void + { + $this->myName = $myName; + } + + public function getDate(): \DateTime + { + return $this->date; + } + + public function setDate(\DateTime $date): void + { + $this->date = $date; + } + + public function getDateTimeImmutable(): \DateTimeImmutable + { + return $this->dateTimeImmutable; + } + + public function setDateTimeImmutable(\DateTimeImmutable $dateTimeImmutable): void + { + $this->dateTimeImmutable = $dateTimeImmutable; + } + + public function isBool(): bool + { + return $this->bool; + } + + public function setBool(bool $bool): void + { + $this->bool = $bool; + } +} diff --git a/tests/fixtures/ValueObject/WrongObject.php b/tests/fixtures/ValueObject/WrongObject.php new file mode 100644 index 00000000..3a3faec9 --- /dev/null +++ b/tests/fixtures/ValueObject/WrongObject.php @@ -0,0 +1,24 @@ +wrong; + } + + /** + * @param mixed $wrong + */ + public function setWrong($wrong): void + { + $this->wrong = $wrong; + } +} diff --git a/tests/units/Ting/Repository/HydratorValueObject.php b/tests/units/Ting/Repository/HydratorValueObject.php new file mode 100644 index 00000000..3f2a343d --- /dev/null +++ b/tests/units/Ting/Repository/HydratorValueObject.php @@ -0,0 +1,241 @@ +calling($mockMysqliResult)->fetch_fields = function () { + $fields = []; + $stdClass = new \stdClass(); + $stdClass->name = 'firstname'; + $stdClass->orgname = 'boo_firstname'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_VAR_STRING; + $fields[] = $stdClass; + + $stdClass = new \stdClass(); + $stdClass->name = 'name'; + $stdClass->orgname = 'boo_name'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_VAR_STRING; + $fields[] = $stdClass; + return $fields; + }; + + $result = new Result(); + $result->setResult($mockMysqliResult); + $result->setConnectionName('connectionName'); + $result->setDatabase('database'); + + $this + ->if($hydrator = new \CCMBenchmark\Ting\Repository\HydratorValueObject(Bouh::class)) + ->then($iterator = $hydrator->setResult($result)->getIterator()) + ->then($bouh = $iterator->current()) + ->object($bouh) + ->isInstanceOf('tests\fixtures\ValueObject\Bouh') + ->string($bouh->getName()) + ->isIdenticalTo('Robez-Masson') + ->string($bouh->getFirstname()) + ->isIdenticalTo('Sylvain'); + } + + public function testHydrateShouldSupportNativeTypes() + { + $mockMysqliResult = new \mock\tests\fixtures\FakeDriver\MysqliResult([[1, 4.2, 'Robez-Masson', '2025-01-01 00:00:00'/*, '2025-02-28 13:37:42'*/, 0]]); + $this->calling($mockMysqliResult)->fetch_fields = function () { + $fields = []; + $stdClass = new \stdClass(); + $stdClass->name = 'nb'; + $stdClass->orgname = 'boo_nb'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_INT24; + $fields[] = $stdClass; + + $stdClass = new \stdClass(); + $stdClass->name = 'avg'; + $stdClass->orgname = 'boo_avg'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_DECIMAL; + $fields[] = $stdClass; + + $stdClass = new \stdClass(); + $stdClass->name = 'my_name'; + $stdClass->orgname = 'boo_my_name'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_VAR_STRING; + $fields[] = $stdClass; + +// $stdClass = new \stdClass(); +// $stdClass->name = 'date'; +// $stdClass->orgname = 'boo_date'; +// $stdClass->table = 'bouh'; +// $stdClass->orgtable = 'T_BOUH_BOO'; +// $stdClass->type = MYSQLI_TYPE_DATETIME; +// $fields[] = $stdClass; +// +// $stdClass = new \stdClass(); +// $stdClass->name = 'dateimmutable'; +// $stdClass->orgname = 'boo_dateimmutable'; +// $stdClass->table = 'bouh'; +// $stdClass->orgtable = 'T_BOUH_BOO'; +// $stdClass->type = MYSQLI_TYPE_DATETIME; +// $fields[] = $stdClass; + + $stdClass = new \stdClass(); + $stdClass->name = 'bool'; + $stdClass->orgname = 'boo_bool'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_INT24; + $fields[] = $stdClass; + + return $fields; + }; + + $result = new Result(); + $result->setResult($mockMysqliResult); + $result->setConnectionName('connectionName'); + $result->setDatabase('database'); + + $this + ->if($hydrator = new \CCMBenchmark\Ting\Repository\HydratorValueObject(BouhWithNativeType::class)) + ->then($iterator = $hydrator->setResult($result)->getIterator()) + ->then($bouh = $iterator->current()) + ->object($bouh) + ->isInstanceOf('tests\fixtures\ValueObject\BouhWithNativeType') + ; + } + + public function testHydrateShouldHydrateObjectViaConsructor() + { + $mockMysqliResult = new \mock\tests\fixtures\FakeDriver\MysqliResult([['Sylvain', 'Robez-Masson']]); + $this->calling($mockMysqliResult)->fetch_fields = function () { + $fields = []; + $stdClass = new \stdClass(); + $stdClass->name = 'firstname'; + $stdClass->orgname = 'boo_firstname'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_VAR_STRING; + $fields[] = $stdClass; + + $stdClass = new \stdClass(); + $stdClass->name = 'name'; + $stdClass->orgname = 'boo_name'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_VAR_STRING; + $fields[] = $stdClass; + return $fields; + }; + + $result = new Result(); + $result->setResult($mockMysqliResult); + $result->setConnectionName('connectionName'); + $result->setDatabase('database'); + + $this + ->given($this->function->method_exists = true) + ->then($hydrator = new \CCMBenchmark\Ting\Repository\HydratorValueObject(BouhWithConstruct::class)) + ->then($iterator = $hydrator->setResult($result)->getIterator()) + ->when($bouh = $iterator->current()) + ->then($this->function('method_exists')->wasCalled()->once()) + ->object($bouh) + ->isInstanceOf('tests\fixtures\ValueObject\BouhWithConstruct') + ->string($bouh->getName()) + ->isIdenticalTo('Robez-Masson') + ->string($bouh->getFirstname()) + ->isIdenticalTo('Sylvain'); + } + + public function testHydrateShouldThrowAnExceptionWhenResultAndObjectDoNotMatch() + { + $mockMysqliResult = new \mock\tests\fixtures\FakeDriver\MysqliResult([['Sylvain', 'Robez-Masson']]); + $this->calling($mockMysqliResult)->fetch_fields = function () { + $fields = []; + $stdClass = new \stdClass(); + $stdClass->name = 'firstname'; + $stdClass->orgname = 'boo_firstname'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_VAR_STRING; + $fields[] = $stdClass; + + $stdClass = new \stdClass(); + $stdClass->name = 'name'; + $stdClass->orgname = 'boo_name'; + $stdClass->table = 'bouh'; + $stdClass->orgtable = 'T_BOUH_BOO'; + $stdClass->type = MYSQLI_TYPE_VAR_STRING; + $fields[] = $stdClass; + return $fields; + }; + + $result = new Result(); + $result->setResult($mockMysqliResult); + $result->setConnectionName('connectionName'); + $result->setDatabase('database'); + + $this + ->if($hydrator = new \CCMBenchmark\Ting\Repository\HydratorValueObject(WrongObject::class)) + ->and($iterator = $hydrator->setResult($result)->getIterator()) + ->exception(function () use ($iterator) { + $var = $iterator->current(); + })->isInstanceOf(Exception::class)->hasMessage('There is no setter for column "firstname"') + ; + } + + + public function testCountShouldReturn2() + { + $result = new \mock\CCMBenchmark\Ting\Driver\Mysqli\Result(); + + $this->calling($result)->getNumRows = 2; + + $this + ->if($hydrator = new \CCMBenchmark\Ting\Repository\HydratorSingleObject()) + ->then($hydrator->setResult($result)) + ->integer($hydrator->count()) + ->isIdenticalTo(2); + } +}