Skip to content

Commit

Permalink
Merge pull request #4 from parable-php/develop
Browse files Browse the repository at this point in the history
Add PropertyTypeDeterminer
  • Loading branch information
Robin de Graaf authored Dec 17, 2019
2 parents d775b6e + 5361bab commit cd44e70
Show file tree
Hide file tree
Showing 10 changed files with 560 additions and 96 deletions.
Binary file added .DS_Store
Binary file not shown.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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_
Expand Down
107 changes: 59 additions & 48 deletions src/AbstractEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Parable\Orm;

use Parable\Di\Container;

abstract class AbstractEntity
{
/**
Expand All @@ -11,7 +13,7 @@ abstract class AbstractEntity

public function getPrimaryKey(string $key)
{
$this->validatePrimaryKey($key);
$this->validatePrimaryKeyExistsOnEntity($key);

return $this->{$key};
}
Expand All @@ -21,85 +23,52 @@ 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();

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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
));
}
}
}
2 changes: 1 addition & 1 deletion src/AbstractRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/Features/HasTypedProperties.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types=1);

namespace Parable\Orm\Features;

interface HasTypedProperties
{
public function getPropertyType(string $property): ?int;
}
137 changes: 137 additions & 0 deletions src/PropertyTypeDeterminer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php declare(strict_types=1);

namespace Parable\Orm;

use DateTimeImmutable;
use Parable\Orm\Features\HasTypedProperties;

class PropertyTypeDeterminer
{
public const TYPE_INT = 0;
public const TYPE_DATE = 1;
public const TYPE_TIME = 2;
public const TYPE_DATETIME = 3;

public static function typeProperty(AbstractEntity $entity, string $property, $value)
{
if (!($entity instanceof HasTypedProperties)) {
return $value;
}

$type = $entity->getPropertyType($property);

if ($type === null || $value === null) {
return $value;
}

$value_original = $value;

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(sprintf(
"Could not type '%s' as TYPE_DATE with format %s",
$value_original,
Database::DATE_SQL
));
}

break;

case self::TYPE_TIME:
$value = DateTimeImmutable::createFromFormat(Database::TIME_SQL, $value);

if ($value === false) {
throw new Exception(sprintf(
"Could not type '%s' as TYPE_TIME with format %s",
$value_original,
Database::TIME_SQL
));
}

break;

case self::TYPE_DATETIME:
$value = DateTimeImmutable::createFromFormat(Database::DATETIME_SQL, $value);

if ($value === false) {
throw new Exception(sprintf(
"Could not type '%s' as TYPE_DATETIME with format %s",
$value_original,
Database::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_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);
}

break;

case self::TYPE_TIME:
$type = 'TYPE_TIME';

if ($value instanceof DateTimeImmutable) {
return $value->format(Database::TIME_SQL);
}

break;

case self::TYPE_DATETIME:
$type = 'TYPE_DATETIME';

if ($value instanceof DateTimeImmutable) {
return $value->format(Database::DATETIME_SQL);
}

break;
}

throw new Exception(sprintf(
"Could not untype '%s' as %s",
$value,
$type
));
}
}
Loading

0 comments on commit cd44e70

Please sign in to comment.