From 96bc09597ce7ea2c2dd9229c94ded7a49ac92a28 Mon Sep 17 00:00:00 2001 From: Robin de Graaf Date: Tue, 17 Dec 2019 20:38:46 +0000 Subject: [PATCH 1/2] Add PropertyTypeDeterminer --- CHANGELOG.md | 10 ++ src/AbstractEntity.php | 107 ++++++++++-------- src/AbstractRepository.php | 2 +- src/Features/HasTypedProperties.php | 8 ++ src/PropertyTypeDeterminer.php | 99 ++++++++++++++++ .../Classes/TestEntityWithTypedProperties.php | 107 ++++++++++++++++++ tests/Classes/TestRepositoryForTyped.php | 23 ++++ tests/EntityTest.php | 103 +++++++++-------- tests/RepositoryTest.php | 67 ++++++++++- 9 files changed, 430 insertions(+), 96 deletions(-) create mode 100644 src/Features/HasTypedProperties.php create mode 100644 src/PropertyTypeDeterminer.php create mode 100644 tests/Classes/TestEntityWithTypedProperties.php create mode 100644 tests/Classes/TestRepositoryForTyped.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fdc306..6786613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Parable PHP ORM +## 0.7.0 + +_Changes_ + +- Time for more type casting! By implementing `HasTypedProperties` and the function `getPropertyType(string $property): ?int`, you can optionally allow value type casting from and to the database. Supported types are defined on `PropertyTypeDeterminer` as `TYPE_INT`, `TYPE_DATE`, `TYPE_TIME` and `TYPE_DATETIME`. The last three will return a `DateTimeImmutable` instance. The formats of `DATE_SQL`, `DATE_TIME`, `DATETIME_SQL` are forced for these. + - These are cast TO these types after retrieving from the database. This allows, for example, type hinting the setter for created at: `setCreatedAt(DateTimeImmutable $createdAt)` and the getter: `getCreatedAt(): DateTimeImmutable`. + - These are transformed back to string values before saving, because `toArray()` does so. +- `PropertyTypeDeterminer` offers two static methods (`typeProperty` and `untypeProperty`) which will attempt to type, if required, the passed property and value on the entity. +- New instances of entities are now built using the DI Container. This allows DI to be used in entities if you so wish. + ## 0.6.1 _Changes_ diff --git a/src/AbstractEntity.php b/src/AbstractEntity.php index 4fdfa40..cc9057c 100644 --- a/src/AbstractEntity.php +++ b/src/AbstractEntity.php @@ -2,6 +2,8 @@ namespace Parable\Orm; +use Parable\Di\Container; + abstract class AbstractEntity { /** @@ -11,7 +13,7 @@ abstract class AbstractEntity public function getPrimaryKey(string $key) { - $this->validatePrimaryKey($key); + $this->validatePrimaryKeyExistsOnEntity($key); return $this->{$key}; } @@ -21,67 +23,45 @@ public function setPrimaryKey(string $key, $value): void $this->{$key} = $value; } - public function markAsOriginal(): void - { - $this->originalProperties = $this->toArray(); - } - public function hasBeenMarkedAsOriginal(): bool { return $this->originalProperties !== []; } - public static function fromDatabaseItem(string $primaryKey, array $values): self + public static function fromDatabaseItem(Container $container, string $primaryKey, array $values): self { - $entity = new static(); - - $entity->validatePrimaryKey($primaryKey); + $entity = $container->build(static::class); - $entity->setPrimaryKey($primaryKey, $values[$primaryKey] ?? null); + $entity->validatePrimaryKeyExistsOnEntity($primaryKey); - if ($entity->getPrimaryKey($primaryKey) === null) { + if (!isset($values[$primaryKey])) { throw new Exception(sprintf( - "Could not set primary key '%s' on Entity %s from values", + "Could not set primary key '%s' on entity %s from database values", $primaryKey, static::class )); } + $primaryKeyValue = PropertyTypeDeterminer::typeProperty($entity, $primaryKey, $values[$primaryKey] ?? null); + + $entity->setPrimaryKey($primaryKey, $primaryKeyValue ?? null); + foreach ($values as $property => $value) { if (!property_exists($entity, $property)) { throw new Exception(sprintf( - "Property '%s' does not exist on Entity %s", + "Property '%s' does not exist on entity %s", $property, static::class )); } - if ($property === $primaryKey) { + if ($property === $primaryKey || $value === null) { continue; } - if (strpos($property, '_') !== false) { - $setter = 'set'; - - $propertyParts = explode('_', $property); - foreach ($propertyParts as $propertyPart) { - $setter .= ucfirst($propertyPart); - } - } else { - $setter = 'set' . ucfirst($property); - } + $setter = $entity->getSetterForProperty($property); - if (!method_exists($entity, $setter)) { - throw new Exception(sprintf( - "Setter method '%s' not defined on Entity %s", - $setter, - static::class - )); - } - - if ($value !== null) { - $entity->{$setter}($value); - } + $entity->{$setter}(PropertyTypeDeterminer::typeProperty($entity, $property, $value)); } $entity->markAsOriginal(); @@ -89,17 +69,6 @@ public static function fromDatabaseItem(string $primaryKey, array $values): self return $entity; } - public function validatePrimaryKey(string $key): void - { - if (!property_exists($this, $key)) { - throw new Exception(sprintf( - "Primary key property '%s' does not exist on Entity %s", - $key, - static::class - )); - } - } - public function toArray(): array { $array = (array)$this; @@ -109,6 +78,8 @@ public function toArray(): array $key = str_replace('*', '', $key); $key = trim(str_replace(static::class, '', $key)); + $value = PropertyTypeDeterminer::untypeProperty($this, $key, $value); + if ($value !== null && !is_scalar($value)) { continue; } @@ -158,8 +129,48 @@ public function toArrayWithOnlyChanges(): array return $array; } - public function getProperties(): array + public function markAsOriginal(): void + { + $this->originalProperties = $this->toArray(); + } + + protected function getSetterForProperty(string $property): string + { + if (strpos($property, '_') !== false) { + $setter = 'set'; + + $propertyParts = explode('_', $property); + foreach ($propertyParts as $propertyPart) { + $setter .= ucfirst($propertyPart); + } + } else { + $setter = 'set' . ucfirst($property); + } + + if (!method_exists($this, $setter)) { + throw new Exception(sprintf( + "Setter method '%s' not defined on entity %s", + $setter, + static::class + )); + } + + return $setter; + } + + protected function getProperties(): array { return array_keys($this->toArray()); } + + protected function validatePrimaryKeyExistsOnEntity(string $key): void + { + if (!property_exists($this, $key)) { + throw new Exception(sprintf( + "Primary key property '%s' does not exist on entity %s", + $key, + static::class + )); + } + } } diff --git a/src/AbstractRepository.php b/src/AbstractRepository.php index add0045..b3c45b2 100644 --- a/src/AbstractRepository.php +++ b/src/AbstractRepository.php @@ -340,7 +340,7 @@ protected function deleteByPrimaryKeys(array $primaryKeys): void protected function createEntityFromArrayItem(array $item): AbstractEntity { - return $this->getEntityClass()::fromDatabaseItem($this->getPrimaryKey(), $item); + return $this->getEntityClass()::fromDatabaseItem($this->container, $this->getPrimaryKey(), $item); } protected function getPrimaryKeysFromEntities(array $entities): array diff --git a/src/Features/HasTypedProperties.php b/src/Features/HasTypedProperties.php new file mode 100644 index 0000000..01062db --- /dev/null +++ b/src/Features/HasTypedProperties.php @@ -0,0 +1,8 @@ +getPropertyType($property); + + if ($type === null || $value === null) { + return $value; + } + + $value_original = $value; + + switch ($type) { + case self::TYPE_INT: + return (int)$value; + + case self::TYPE_DATE: + $value = DateTimeImmutable::createFromFormat(Database::DATE_SQL, $value); + + if ($value === false) { + throw new Exception('Cannot type ' . $value_original . ' as DATE_SQL'); + } + + break; + + case self::TYPE_TIME: + $value = DateTimeImmutable::createFromFormat(Database::TIME_SQL, $value); + + if ($value === false) { + throw new Exception('Cannot type ' . $value_original . ' as TIME_SQL'); + } + + break; + + case self::TYPE_DATETIME: + $value = DateTimeImmutable::createFromFormat(Database::DATETIME_SQL, $value); + + if ($value === false) { + throw new Exception('Cannot type ' . $value_original . ' as DATETIME_SQL'); + } + } + + return $value; + } + + public static function untypeProperty(AbstractEntity $entity, string $property, $value) + { + if (!($entity instanceof HasTypedProperties)) { + return $value; + } + + $type = $entity->getPropertyType($property); + + if ($type === null || $value === null) { + return $value; + } + + switch ($type) { + case self::TYPE_DATE: + if ($value instanceof DateTimeImmutable) { + return $value->format(Database::DATE_SQL); + } + + break; + + case self::TYPE_TIME: + if ($value instanceof DateTimeImmutable) { + return $value->format(Database::TIME_SQL); + } + + break; + + case self::TYPE_DATETIME: + if ($value instanceof DateTimeImmutable) { + return $value->format(Database::DATETIME_SQL); + } + + break; + } + + return $value; + } +} diff --git a/tests/Classes/TestEntityWithTypedProperties.php b/tests/Classes/TestEntityWithTypedProperties.php new file mode 100644 index 0000000..a89beca --- /dev/null +++ b/tests/Classes/TestEntityWithTypedProperties.php @@ -0,0 +1,107 @@ +id = $id; + $this->date = $date; + $this->time = $time; + $this->datetime = $datetime; + $this->updated_at = $updated_at; + } + + public function getPropertyType(string $property): ?int + { + switch ($property) { + case 'id': + return PropertyTypeDeterminer::TYPE_INT; + + case 'date': + return PropertyTypeDeterminer::TYPE_DATE; + + case 'time': + return PropertyTypeDeterminer::TYPE_TIME; + + case 'datetime': + case 'updated_at': + return PropertyTypeDeterminer::TYPE_DATETIME; + } + + return null; + } + + public function getId(): ?int + { + return $this->id; + } + + public function setId(int $id): void + { + $this->id = $id; + } + + public function getDate(): ?DateTimeImmutable + { + return $this->date; + } + + public function setDate(DateTimeImmutable $date): void + { + $this->date = $date; + } + + public function getTime(): ?DateTimeImmutable + { + return $this->time; + } + + public function setTime(DateTimeImmutable $time): void + { + $this->time = $time; + } + + public function getDatetime(): ?DateTimeImmutable + { + return $this->datetime; + } + + public function setDatetime(DateTimeImmutable $datetime): void + { + $this->datetime = $datetime; + } + + public function getUpdatedAt(): ?DateTimeImmutable + { + return $this->updated_at; + } + + public function setUpdatedAt(DateTimeImmutable $updated_at): void + { + $this->updated_at = $updated_at; + } + + public function markUpdatedAt(): void + { + $this->setUpdatedAt(new DateTimeImmutable()); + } +} diff --git a/tests/Classes/TestRepositoryForTyped.php b/tests/Classes/TestRepositoryForTyped.php new file mode 100644 index 0000000..6b804b2 --- /dev/null +++ b/tests/Classes/TestRepositoryForTyped.php @@ -0,0 +1,23 @@ + 123, - 'name' => 'User McReady', - 'created_at' => $createdAt, - ]); + $entity = TestEntity::fromDatabaseItem( + new Container(), + 'id', + [ + 'id' => 123, + 'name' => 'User McReady', + 'created_at' => $createdAt, + ] + ); self::assertSame( [ @@ -62,55 +69,37 @@ public function testFromDatabaseItemBreaksIfIdOmitted(): void { $this->expectException(Exception::class); $this->expectExceptionMessage( - "Could not set primary key 'id' on Entity Parable\Orm\Tests\Classes\TestEntity from values" + "Could not set primary key 'id' on entity Parable\Orm\Tests\Classes\TestEntity from database values" ); - TestEntity::fromDatabaseItem('id', []); + TestEntity::fromDatabaseItem(new Container(), 'id', []); } public function testFromDatabaseItemBreaksIfInvalidValuePassed(): void { $this->expectException(Exception::class); $this->expectExceptionMessage( - "Property 'bloop' does not exist on Entity Parable\Orm\Tests\Classes\TestEntity" + "Property 'bloop' does not exist on entity Parable\Orm\Tests\Classes\TestEntity" ); - TestEntity::fromDatabaseItem('id', ['id' => 123, 'bloop' => 'what']); + TestEntity::fromDatabaseItem(new Container(), 'id', ['id' => 123, 'bloop' => 'what']); } public function testFromDatabaseItemBreaksOnMissingSetters(): void { $this->expectException(Exception::class); $this->expectExceptionMessage( - "Setter method 'setName' not defined on Entity Parable\Orm\Tests\Classes\TestEntityWithMissingSetters" + "Setter method 'setName' not defined on entity Parable\Orm\Tests\Classes\TestEntityWithMissingSetters" ); - TestEntityWithMissingSetters::fromDatabaseItem('id', [ - 'id' => 123, - 'name' => 'User McReady', - ]); - } - - public function testValidatePrivateKeyDoesNothingForValidKey(): void - { - $entity = new TestEntity(); - - // This would throw if incorrect - $entity->validatePrimaryKey('id'); - - self::expectNotToPerformAssertions(); - } - - public function testValidatePrivateKeyThrowsOnInvalidKey(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage( - "Primary key property 'bloop' does not exist on Entity Parable\Orm\Tests\Classes\TestEntity" + TestEntityWithMissingSetters::fromDatabaseItem( + new Container(), + 'id', + [ + 'id' => 123, + 'name' => 'User McReady', + ] ); - - $entity = new TestEntity(); - - $entity->validatePrimaryKey('bloop'); } public function testGetPrimaryKeyWorks(): void @@ -124,7 +113,7 @@ public function testGetPrimaryKeyThrowsOnInvalidKey(): void { $this->expectException(Exception::class); $this->expectExceptionMessage( - "Primary key property 'bloop' does not exist on Entity Parable\Orm\Tests\Classes\TestEntity" + "Primary key property 'bloop' does not exist on entity Parable\Orm\Tests\Classes\TestEntity" ); $entity = new TestEntity(); @@ -202,16 +191,6 @@ public function testCreateValidEntityAndToArrayWithOnlyChanges(): void ); } - public function testGetPropertiesReturnsAllProperties(): void - { - $entity = new TestEntity(); - - self::assertSame( - ['id', 'name', 'created_at', 'updated_at'], - $entity->getProperties() - ); - } - public function testMarkCreatedAt(): void { $entity = new TestEntity(); @@ -259,4 +238,36 @@ public function testHasBeenMarkedAsOriginal(): void self::assertTrue($entity->hasBeenMarkedAsOriginal()); } + + public function testTypedProperties(): void + { + $entity = new TestEntityWithTypedProperties(); + + $entity->with( + 1, + new DateTimeImmutable('2019-12-01 12:34:45'), + new DateTimeImmutable('2019-12-01 12:34:45'), + new DateTimeImmutable('2019-12-01 12:34:45') + ); + + $id = PropertyTypeDeterminer::typeProperty($entity, 'id', '1'); + $date = PropertyTypeDeterminer::typeProperty($entity, 'date', '2019-12-01'); + $time = PropertyTypeDeterminer::typeProperty($entity, 'time', '05:00:00'); + $datetime = PropertyTypeDeterminer::typeProperty($entity, 'datetime', '2019-12-01 05:00:00'); + + self::assertIsInt($id); + self::assertInstanceOf(DateTimeImmutable::class, $date); + self::assertInstanceOf(DateTimeImmutable::class, $time); + self::assertInstanceOf(DateTimeImmutable::class, $datetime); + + $id = PropertyTypeDeterminer::untypeProperty($entity, 'id', $id); + $date = PropertyTypeDeterminer::untypeProperty($entity, 'date', $date); + $time = PropertyTypeDeterminer::untypeProperty($entity, 'time', $time); + $datetime = PropertyTypeDeterminer::untypeProperty($entity, 'datetime', $datetime); + + self::assertSame(1, $id); + self::assertSame('2019-12-01', $date); + self::assertSame('05:00:00', $time); + self::assertSame('2019-12-01 05:00:00', $datetime); + } } diff --git a/tests/RepositoryTest.php b/tests/RepositoryTest.php index 39f793d..2a3fb72 100644 --- a/tests/RepositoryTest.php +++ b/tests/RepositoryTest.php @@ -2,12 +2,15 @@ namespace Parable\Orm\Tests; +use DateTimeImmutable; use Parable\Di\Container; use Parable\Orm\Database; use Parable\Orm\Exception; use Parable\Orm\Tests\Classes\TestEntity; use Parable\Orm\Tests\Classes\TestEntityWithoutTraits; +use Parable\Orm\Tests\Classes\TestEntityWithTypedProperties; use Parable\Orm\Tests\Classes\TestRepository; +use Parable\Orm\Tests\Classes\TestRepositoryForTyped; use Parable\Orm\Tests\Classes\TestRepositoryWithoutPrimaryKey; use Parable\Query\OrderBy; use Parable\Query\Query; @@ -706,7 +709,7 @@ public function testNoPrimaryKeyThrowsAnException(): void { $this->expectException(Exception::class); $this->expectExceptionMessage( - "Primary key property 'id' does not exist on Entity Parable\Orm\Tests\Classes\TestEntityWithoutPrimaryKey" + "Primary key property 'id' does not exist on entity Parable\Orm\Tests\Classes\TestEntityWithoutPrimaryKey" ); /** @var TestRepositoryWithoutPrimaryKey $repository */ @@ -744,4 +747,66 @@ public function testIsStoredConsidersEntityStoredWithPrimaryKeyAndMarkedAsOrigin self::assertTrue($this->repository->isStored($user1)); } + + public function testTypedEntityWithRepository(): void + { + $this->database->query(" + CREATE TABLE types ( + id INTEGER PRIMARY KEY, + date TEXT DEFAULT NULL, + time TEXT DEFAULT NULL, + datetime TEXT DEFAULT NULL, + updated_at TEXT DEFAULT NULL + ); + "); + + $repository = $this->container->build(TestRepositoryForTyped::class); + + $entity = new TestEntityWithTypedProperties(); + $entity->with( + 1, + new DateTimeImmutable('2019-12-01 12:34:45'), + new DateTimeImmutable('2019-12-01 12:34:45'), + new DateTimeImmutable('2019-12-01 12:34:45') + ); + + $repository->save($entity); + + /** @var TestEntityWithTypedProperties $entity_from_db */ + $entity_from_db = $repository->find(1); + + self::assertIsInt($entity_from_db->getId()); + self::assertInstanceOf(DateTimeImmutable::class, $entity_from_db->getDate()); + self::assertInstanceOf(DateTimeImmutable::class, $entity_from_db->getTime()); + self::assertInstanceOf(DateTimeImmutable::class, $entity_from_db->getDatetime()); + self::assertNull($entity_from_db->getUpdatedAt()); + + $entity_from_db->setDatetime(new DateTimeImmutable('2018-01-15 06:54:32')); + + self::assertSame( + $entity_from_db->toArray(), + [ + 'id' => 1, + 'date' => '2019-12-01', + 'time' => '12:34:45', + 'datetime' => '2018-01-15 06:54:32', + 'updated_at' => null, + ] + ); + + $entity_from_db = $repository->save($entity_from_db); + + self::assertInstanceOf(DateTimeImmutable::class, $entity_from_db->getUpdatedAt()); + + self::assertSame( + $entity_from_db->toArray(), + [ + 'id' => 1, + 'date' => '2019-12-01', + 'time' => '12:34:45', + 'datetime' => '2018-01-15 06:54:32', + 'updated_at' => $entity_from_db->getUpdatedAt()->format(Database::DATETIME_SQL), + ] + ); + } } From 5361babdf3291887f138289413d710f3def99d6c Mon Sep 17 00:00:00 2001 From: Robin de Graaf Date: Tue, 17 Dec 2019 21:05:58 +0000 Subject: [PATCH 2/2] Add checks to make sure TYPE_INT values are numeric --- .DS_Store | Bin 0 -> 6148 bytes src/PropertyTypeDeterminer.php | 52 ++++++-- .../Classes/TestEntityWithTypedProperties.php | 20 ++-- tests/EntityTest.php | 112 ++++++++++++++++-- 4 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1ad899dbcdc748036dd701eacb0ec11e243703d4 GIT binary patch literal 6148 zcmeHKyJ`bL3>+mc4$`I(2Sbbi#2M3J zT*oXyY@Q(Y!ZDE|1kgGlC+ZoQs7@HV6)|Fx!@~RZ=JlH_u58Z)4k@C?#6Xc7@{2$qaAbO f?f5y0vab1>=e=-D3_9~cC+cUwb&*MdzgFN1-fR_w literal 0 HcmV?d00001 diff --git a/src/PropertyTypeDeterminer.php b/src/PropertyTypeDeterminer.php index c359d94..345e457 100644 --- a/src/PropertyTypeDeterminer.php +++ b/src/PropertyTypeDeterminer.php @@ -8,9 +8,9 @@ class PropertyTypeDeterminer { public const TYPE_INT = 0; - public const TYPE_DATETIME = 1; - public const TYPE_DATE = 2; - public const TYPE_TIME = 3; + public const TYPE_DATE = 1; + public const TYPE_TIME = 2; + public const TYPE_DATETIME = 3; public static function typeProperty(AbstractEntity $entity, string $property, $value) { @@ -28,13 +28,24 @@ public static function typeProperty(AbstractEntity $entity, string $property, $v switch ($type) { case self::TYPE_INT: + if (!is_numeric($value)) { + throw new Exception(sprintf( + "Could not type '%s' as TYPE_INT", + $value_original + )); + } + return (int)$value; case self::TYPE_DATE: $value = DateTimeImmutable::createFromFormat(Database::DATE_SQL, $value); if ($value === false) { - throw new Exception('Cannot type ' . $value_original . ' as DATE_SQL'); + throw new Exception(sprintf( + "Could not type '%s' as TYPE_DATE with format %s", + $value_original, + Database::DATE_SQL + )); } break; @@ -43,7 +54,11 @@ public static function typeProperty(AbstractEntity $entity, string $property, $v $value = DateTimeImmutable::createFromFormat(Database::TIME_SQL, $value); if ($value === false) { - throw new Exception('Cannot type ' . $value_original . ' as TIME_SQL'); + throw new Exception(sprintf( + "Could not type '%s' as TYPE_TIME with format %s", + $value_original, + Database::TIME_SQL + )); } break; @@ -52,7 +67,11 @@ public static function typeProperty(AbstractEntity $entity, string $property, $v $value = DateTimeImmutable::createFromFormat(Database::DATETIME_SQL, $value); if ($value === false) { - throw new Exception('Cannot type ' . $value_original . ' as DATETIME_SQL'); + throw new Exception(sprintf( + "Could not type '%s' as TYPE_DATETIME with format %s", + $value_original, + Database::DATETIME_SQL + )); } } @@ -72,7 +91,18 @@ public static function untypeProperty(AbstractEntity $entity, string $property, } switch ($type) { + case self::TYPE_INT: + $type = 'TYPE_INT'; + + if (is_numeric($value)) { + return (int)$value; + } + + break; + case self::TYPE_DATE: + $type = 'TYPE_DATE'; + if ($value instanceof DateTimeImmutable) { return $value->format(Database::DATE_SQL); } @@ -80,6 +110,8 @@ public static function untypeProperty(AbstractEntity $entity, string $property, break; case self::TYPE_TIME: + $type = 'TYPE_TIME'; + if ($value instanceof DateTimeImmutable) { return $value->format(Database::TIME_SQL); } @@ -87,6 +119,8 @@ public static function untypeProperty(AbstractEntity $entity, string $property, break; case self::TYPE_DATETIME: + $type = 'TYPE_DATETIME'; + if ($value instanceof DateTimeImmutable) { return $value->format(Database::DATETIME_SQL); } @@ -94,6 +128,10 @@ public static function untypeProperty(AbstractEntity $entity, string $property, break; } - return $value; + throw new Exception(sprintf( + "Could not untype '%s' as %s", + $value, + $type + )); } } diff --git a/tests/Classes/TestEntityWithTypedProperties.php b/tests/Classes/TestEntityWithTypedProperties.php index a89beca..f604f8d 100644 --- a/tests/Classes/TestEntityWithTypedProperties.php +++ b/tests/Classes/TestEntityWithTypedProperties.php @@ -17,11 +17,11 @@ class TestEntityWithTypedProperties extends AbstractEntity implements SupportsUp protected $updated_at; public function with( - int $id = null, - DateTimeImmutable $date = null, - DateTimeImmutable $time = null, - DateTimeImmutable $datetime = null, - DateTimeImmutable $updated_at = null + $id = null, + $date = null, + $time = null, + $datetime = null, + $updated_at = null ): void { $this->id = $id; $this->date = $date; @@ -50,7 +50,7 @@ public function getPropertyType(string $property): ?int return null; } - public function getId(): ?int + public function getId() { return $this->id; } @@ -60,7 +60,7 @@ public function setId(int $id): void $this->id = $id; } - public function getDate(): ?DateTimeImmutable + public function getDate() { return $this->date; } @@ -70,7 +70,7 @@ public function setDate(DateTimeImmutable $date): void $this->date = $date; } - public function getTime(): ?DateTimeImmutable + public function getTime() { return $this->time; } @@ -80,7 +80,7 @@ public function setTime(DateTimeImmutable $time): void $this->time = $time; } - public function getDatetime(): ?DateTimeImmutable + public function getDatetime() { return $this->datetime; } @@ -90,7 +90,7 @@ public function setDatetime(DateTimeImmutable $datetime): void $this->datetime = $datetime; } - public function getUpdatedAt(): ?DateTimeImmutable + public function getUpdatedAt() { return $this->updated_at; } diff --git a/tests/EntityTest.php b/tests/EntityTest.php index 42a0362..e9ef35c 100644 --- a/tests/EntityTest.php +++ b/tests/EntityTest.php @@ -4,6 +4,7 @@ use DateTimeImmutable; use Parable\Di\Container; +use Parable\Orm\Database; use Parable\Orm\Exception; use Parable\Orm\PropertyTypeDeterminer; use Parable\Orm\Tests\Classes\TestEntity; @@ -244,30 +245,121 @@ public function testTypedProperties(): void $entity = new TestEntityWithTypedProperties(); $entity->with( - 1, - new DateTimeImmutable('2019-12-01 12:34:45'), - new DateTimeImmutable('2019-12-01 12:34:45'), - new DateTimeImmutable('2019-12-01 12:34:45') + '1', + '2019-12-01', + '12:34:45', + '2019-12-01 12:34:45' ); - $id = PropertyTypeDeterminer::typeProperty($entity, 'id', '1'); - $date = PropertyTypeDeterminer::typeProperty($entity, 'date', '2019-12-01'); - $time = PropertyTypeDeterminer::typeProperty($entity, 'time', '05:00:00'); - $datetime = PropertyTypeDeterminer::typeProperty($entity, 'datetime', '2019-12-01 05:00:00'); + $id = PropertyTypeDeterminer::typeProperty($entity, 'id', $entity->getId()); + $date = PropertyTypeDeterminer::typeProperty($entity, 'date', $entity->getDate()); + $time = PropertyTypeDeterminer::typeProperty($entity, 'time', $entity->getTime()); + $datetime = PropertyTypeDeterminer::typeProperty($entity, 'datetime', $entity->getDatetime()); + $updatedAt = PropertyTypeDeterminer::typeProperty($entity, 'datetime', $entity->getUpdatedAt()); self::assertIsInt($id); self::assertInstanceOf(DateTimeImmutable::class, $date); self::assertInstanceOf(DateTimeImmutable::class, $time); self::assertInstanceOf(DateTimeImmutable::class, $datetime); + self::assertNull($updatedAt); $id = PropertyTypeDeterminer::untypeProperty($entity, 'id', $id); $date = PropertyTypeDeterminer::untypeProperty($entity, 'date', $date); $time = PropertyTypeDeterminer::untypeProperty($entity, 'time', $time); $datetime = PropertyTypeDeterminer::untypeProperty($entity, 'datetime', $datetime); + $updatedAt = PropertyTypeDeterminer::untypeProperty($entity, 'datetime', $updatedAt); self::assertSame(1, $id); self::assertSame('2019-12-01', $date); - self::assertSame('05:00:00', $time); - self::assertSame('2019-12-01 05:00:00', $datetime); + self::assertSame('12:34:45', $time); + self::assertSame('2019-12-01 12:34:45', $datetime); + self::assertNull($updatedAt); + } + + public function testTypingIntThrowsOnInvalidValue(): void + { + $entity = new TestEntityWithTypedProperties(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Could not type 'bla' as TYPE_INT"); + + PropertyTypeDeterminer::typeProperty($entity, 'id', 'bla'); + } + + public function testTypingDateTypeThrowsOnInvalidValue(): void + { + $entity = new TestEntityWithTypedProperties(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Could not type 'bla' as TYPE_DATE with format " . Database::DATE_SQL); + + PropertyTypeDeterminer::typeProperty($entity, 'date', 'bla'); + } + + public function testTypingTimeTypeThrowsOnInvalidValue(): void + { + $entity = new TestEntityWithTypedProperties(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Could not type 'bla' as TYPE_TIME with format " . Database::TIME_SQL); + + PropertyTypeDeterminer::typeProperty($entity, 'time', 'bla'); + } + + public function testTypingDateTimeTypeThrowsOnInvalidValue(): void + { + $entity = new TestEntityWithTypedProperties(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Could not type 'bla' as TYPE_DATETIME with format " . Database::DATETIME_SQL); + + PropertyTypeDeterminer::typeProperty($entity, 'datetime', 'bla'); + } + + public function testUntypeOnNonNumericIntThrows(): void + { + $entity = new TestEntityWithTypedProperties(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Could not untype 'bla' as TYPE_INT"); + + PropertyTypeDeterminer::untypeProperty($entity, 'id', 'bla'); + } + + public function testUntypingDateTypeThrowsOnInvalidValue(): void + { + $entity = new TestEntityWithTypedProperties(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Could not untype 'bla' as TYPE_DATE"); + + PropertyTypeDeterminer::untypeProperty($entity, 'date', 'bla'); + } + + public function testUntypingTimeTypeThrowsOnInvalidValue(): void + { + $entity = new TestEntityWithTypedProperties(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Could not untype 'bla' as TYPE_TIME"); + + PropertyTypeDeterminer::untypeProperty($entity, 'time', 'bla'); + } + + public function testUntypingDateTimeTypeThrowsOnInvalidValue(): void + { + $entity = new TestEntityWithTypedProperties(); + + $this->expectException(Exception::class); + $this->expectExceptionMessage("Could not untype 'bla' as TYPE_DATETIME"); + + PropertyTypeDeterminer::untypeProperty($entity, 'datetime', 'bla'); + } + + public function testUntypeOnNullDoesNothing(): void + { + $entity = new TestEntityWithTypedProperties(); + + self::assertNull(PropertyTypeDeterminer::untypeProperty($entity, 'datetime', null)); } }