Skip to content

Commit

Permalink
entity: date time type works only with DateTimeImmutable (BC break!)
Browse files Browse the repository at this point in the history
  • Loading branch information
hrach committed Dec 15, 2016
1 parent 7428c0f commit 7627376
Show file tree
Hide file tree
Showing 18 changed files with 95 additions and 129 deletions.
20 changes: 11 additions & 9 deletions doc/entity.texy
Expand Up @@ -7,12 +7,11 @@ Data are accessible as properties. You have to define all properties that should

/--php
/**
* @property int $id {primary}
* @property string $name
* @property DateTime $born
* @property string|null $web
*
* @property-read int $age
* @property int $id {primary}
* @property string $name
* @property DateTimeImmutable $born
* @property string|null $web
* @property-read int $age
*/
class Member extends Nextras\Orm\Entity\Entity
{
Expand All @@ -23,13 +22,16 @@ Phpdoc property definition consists of its type and name. If you would like to u

If you put some value into the property, the value will be validated by property type annotation. It's type casting is performed if it is possible and safe. Supported types are `null`, `string`, `int`, `float`, `array`, `mixed` and object types. Validation is provided on all properties, except for properties defined with container - in that case validation should do the container.

Nextras Orm also provides enhanced support for DateTime handlings. However, only "safe" `DateTimeImmutable` instances are supported as a entity property type. You may put common `DateTime` instance as a value, but it will be automatically converted to DateTimeImmutable. Also, auto date string conversion is supported.

"Property access" is the easiest way to work with the data, although, such feature is not defined in `IEntity` interface. To conform the interface, you can use "method access": `getValue()` method for reading, `setValue()` method for setting a property, `hasValue()`, etc. There is a special `getRawValue()` method, which returns raw representation of the value. The raw representation is basically the stored value (a primary key for relationship property).

/--php
$member = new Member();

$member->name = 'Jon';
$member->setValue('name', 'Jon');
$member->born = 'now'; // will automatically convert to DateTimeImmutable

echo $member->name;
echo $member->getValue('name');
Expand Down Expand Up @@ -189,8 +191,8 @@ Virtual modifier marks specific property as virtual - such property won't be sto
/--php
/**
* ...
* @property DateTime $born
* @property-read int $age {virtual}
* @property DateTimeImmutable $born
* @property-read int $age {virtual}
*/
class Member extends Nextras\Orm\Entity\Entity
{
Expand All @@ -201,7 +203,7 @@ class Member extends Nextras\Orm\Entity\Entity
}

$member = new Member();
$member->born = new DateTime('2000-01-01');
$member->born = new DateTimeImmutable('2000-01-01');
echo $member->age;
\--

Expand Down
8 changes: 5 additions & 3 deletions src/Collection/Helpers/ArrayCollectionHelper.php
Expand Up @@ -9,6 +9,8 @@
namespace Nextras\Orm\Collection\Helpers;

use Closure;
use DateTimeImmutable;
use DateTimeInterface;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\Reflection\EntityMetadata;
Expand Down Expand Up @@ -178,7 +180,7 @@ public function createSorter(array $conditions)
} elseif ($_a > $_b) {
return $direction;
}
} elseif ($_a instanceof \Datetime) {
} elseif ($_a instanceof DateTimeInterface) {
if ($_a < $_b) {
return $direction * -1;
} elseif ($_b > $_a) {
Expand Down Expand Up @@ -223,8 +225,8 @@ private function normalizeValue($value, PropertyMetadata $propertyMetadata)
if ($value instanceof IEntity) {
return $value->hasValue('id') ? $value->getValue('id') : null;

} elseif ((isset($propertyMetadata->types['DateTime']) || isset($propertyMetadata->types['datetime'])) && $value !== null && !$value instanceof \DateTime) {
return new \DateTime($value);
} elseif (isset($propertyMetadata->types['datetime']) && $value !== null && !$value instanceof DateTimeInterface) {
return new DateTimeImmutable($value);
}

return $value;
Expand Down
8 changes: 8 additions & 0 deletions src/Entity/Reflection/MetadataParser.php
Expand Up @@ -8,13 +8,16 @@

namespace Nextras\Orm\Entity\Reflection;

use DateTime;
use DateTimeImmutable;
use Nette\Reflection\AnnotationsParser;
use Nette\Reflection\ClassType;
use Nextras\Orm\Collection\ICollection;
use Nextras\Orm\Entity\IProperty;
use Nextras\Orm\InvalidArgumentException;
use Nextras\Orm\InvalidModifierDefinitionException;
use Nextras\Orm\InvalidStateException;
use Nextras\Orm\NotSupportedException;
use Nextras\Orm\Relationships\ManyHasMany;
use Nextras\Orm\Relationships\ManyHasOne;
use Nextras\Orm\Relationships\OneHasMany;
Expand Down Expand Up @@ -192,6 +195,11 @@ protected function parseAnnotationTypes(PropertyMetadata $property, $typesString
$type = $aliases[$typeLower];
} else {
$type = $this->makeFQN($type);
if ($type === DateTimeImmutable::class || is_subclass_of($type, DateTimeImmutable::class)) {
$type = 'datetime';
} elseif ($type === DateTime::class || is_subclass_of($type, DateTime::class)) {
throw new NotSupportedException("Type 'DateTime' in {$this->currentReflection->name}::\${$property->name} property is not supported anymore. Use DateTimeImmutable type.");
}
}
$parsedTypes[$type] = true;
}
Expand Down
18 changes: 0 additions & 18 deletions src/Entity/Reflection/PropertyMetadata.php
Expand Up @@ -69,24 +69,6 @@ public function isValid(& $value)
foreach ($this->types as $type => $_) {
$type = strtolower($type);
if ($type === 'datetime') {
if ($value instanceof \DateTime) {
return true;

} elseif ($value instanceof \DateTimeImmutable) {
$value = new \DateTime($value->format('c'));
return true;

} elseif (is_string($value) && $value !== '') {
$value = new \DateTime($value);
$value->setTimezone(new DateTimeZone(date_default_timezone_get()));
return true;

} elseif (ctype_digit($value)) {
$value = new \DateTime("@{$value}");
return true;
}

} elseif ($type === 'datetimeimmutable') {
if ($value instanceof \DateTimeImmutable) {
return true;

Expand Down
7 changes: 4 additions & 3 deletions tests/cases/integration/Entity/entity.defaultValue.phpt
Expand Up @@ -6,12 +6,13 @@

namespace NextrasTests\Orm\Integration\Entity;

use Mockery;
use DateTimeImmutable;
use Nextras\Orm\Model\IModel;
use NextrasTests\Orm\Author;
use NextrasTests\Orm\TestCase;
use Tester\Assert;


$dic = require_once __DIR__ . '/../../../bootstrap.php';


Expand Down Expand Up @@ -43,7 +44,7 @@ class EntityDefaultValueTest extends TestCase
{
/** @var Author $author */
$author = $this->e(Author::class);
Assert::type('DateTime', $author->born);
Assert::type(DateTimeImmutable::class, $author->born);

$author->born = NULL;
Assert::null($author->born);
Expand Down Expand Up @@ -72,7 +73,7 @@ class EntityDefaultValueTest extends TestCase
$author->name = 'Test';
$this->orm->authors->persistAndFlush($author);

Assert::true($author->born instanceof \DateTime);
Assert::true($author->born instanceof \DateTimeImmutable);
Assert::same('http://www.example.com', $author->web);


Expand Down
Expand Up @@ -7,7 +7,8 @@

namespace NextrasTests\Orm\Integration\Mapper;

use Mockery;
use DateTime;
use DateTimeImmutable;
use NextrasTests\Orm\BookCollection;
use NextrasTests\Orm\DataTestCase;
use Tester\Assert;
Expand Down Expand Up @@ -35,16 +36,16 @@ class DbalPersistAutoupdateMapperTest extends DataTestCase
Assert::null($bookCollection->updatedAt);
$this->orm->bookColletions->persistAndFlush($bookCollection);

Assert::type(\DateTime::class, $bookCollection->updatedAt);
Assert::type(DateTimeImmutable::class, $bookCollection->updatedAt);
$old = $bookCollection->updatedAt;

sleep(1);
$bookCollection->name .= '1';
$this->orm->bookColletions->persistAndFlush($bookCollection);

Assert::type(\DateTime::class, $bookCollection->updatedAt);
Assert::type(DateTimeImmutable::class, $bookCollection->updatedAt);
$new = $bookCollection->updatedAt;
Assert::notEqual($old->format($old::ISO8601), $new->format($new::ISO8601));
Assert::notEqual($old->format(DateTime::ISO8601), $new->format(DateTime::ISO8601));
}
}

Expand Down
47 changes: 8 additions & 39 deletions tests/cases/unit/Entity/Reflection/PropertyMetadata.isValid().phpt
Expand Up @@ -6,15 +6,14 @@

namespace NextrasTests\Orm\Entity\Reflection;

use Mockery;
use DateTime;
use DateTimeImmutable;
use Nette\Utils\ArrayHash;
use Nextras\Orm\Entity\Reflection\MetadataParser;
use Nextras\Orm\Entity\Reflection\EntityMetadata;
use Nextras\Orm\Entity\Reflection\MetadataParser;
use NextrasTests\Orm\TestCase;
use Tester\Assert;
use Tester\Environment;


$dic = require_once __DIR__ . '/../../../../bootstrap.php';

Expand All @@ -27,8 +26,7 @@ $dic = require_once __DIR__ . '/../../../../bootstrap.php';
* @property int $int
* @property bool $boolean
* @property float $float
* @property datetime $datetime
* @property datetimeimmutable $datetimeimmutable
* @property DateTimeImmutable $datetimeimmutable
* @property array $array1
* @property int[] $array2
* @property object $object
Expand Down Expand Up @@ -67,56 +65,27 @@ class PropertyMetadataIsValidTest extends TestCase
}


public function testDateTime()
{
$property = $this->metadata->getProperty('datetime');

$val = new \DateTime();
Assert::true($property->isValid($val));

$val = new \Nextras\Dbal\Utils\DateTime();
Assert::true($property->isValid($val));

$tz = DateTime::createFromFormat('O', '+05:00')->getTimezone(); // hhvm compatibility
$val = new \DateTimeImmutable('now', $tz);
Assert::true($property->isValid($val));
Assert::type('DateTime', $val);
Assert::same($tz->getName(), $val->getTimezone()->getName());

$val = '';
Assert::false($property->isValid($val));

$val = 'now';
Assert::true($property->isValid($val));
Assert::type('DateTime', $val);

$val = time();
Assert::true($property->isValid($val));
Assert::type('DateTime', $val);
}


public function testDateTimeImmutable()
{
$property = $this->metadata->getProperty('datetimeimmutable');

$val = new \DateTimeImmutable();
$val = new DateTimeImmutable();
Assert::true($property->isValid($val));

$val = new \DateTime();
$val = new DateTime();
Assert::true($property->isValid($val));
Assert::type('DateTimeImmutable', $val);
Assert::type(DateTimeImmutable::class, $val);

$val = '';
Assert::false($property->isValid($val));

$val = 'now';
Assert::true($property->isValid($val));
Assert::type('DateTimeImmutable', $val);
Assert::type(DateTimeImmutable::class, $val);

$val = time();
Assert::true($property->isValid($val));
Assert::type('DateTimeImmutable', $val);
Assert::type(DateTimeImmutable::class, $val);
}


Expand Down
22 changes: 11 additions & 11 deletions tests/inc/model/author/Author.php
Expand Up @@ -2,22 +2,22 @@

namespace NextrasTests\Orm;

use DateTime;
use DateTimeImmutable;
use Nextras\Orm\Entity\Entity;
use Nextras\Orm\Relationships\OneHasMany as OHM;


/**
* @property int $id {primary}
* @property string $name
* @property DateTime|NULL $born {default now}
* @property string $web {default "http://www.example.com"}
* @property Author|null $favoriteAuthor {m:1 Author::$favoredBy}
* @property OHM|Author[] $favoredBy {1:m Author::$favoriteAuthor}
* @property OHM|Book[] $books {1:m Book::$author, orderBy=[id, DESC], cascade=[persist, remove]}
* @property OHM|Book[] $translatedBooks {1:m Book::$translator}
* @property OHM|TagFollower[] $tagFollowers {1:m TagFollower::$author, cascade=[persist, remove]}
* @property-read int $age {virtual}
* @property int $id {primary}
* @property string $name
* @property DateTimeImmutable|null $born {default now}
* @property string $web {default "http://www.example.com"}
* @property Author|null $favoriteAuthor {m:1 Author::$favoredBy}
* @property OHM|Author[] $favoredBy {1:m Author::$favoriteAuthor}
* @property OHM|Book[] $books {1:m Book::$author, orderBy=[id, DESC], cascade=[persist, remove]}
* @property OHM|Book[] $translatedBooks {1:m Book::$translator}
* @property OHM|TagFollower[] $tagFollowers {1:m TagFollower::$author, cascade=[persist, remove]}
* @property-read int $age {virtual}
*/
final class Author extends Entity
{
Expand Down
23 changes: 11 additions & 12 deletions tests/inc/model/book/Book.php
Expand Up @@ -2,24 +2,23 @@

namespace NextrasTests\Orm;

use DateTime;
use DateTimeImmutable;
use Nextras\Orm\Entity\Entity;
use Nextras\Orm\Relationships\ManyHasMany as MHM;


/**
* @property int $id {primary}
* @property string $title
* @property Author $author {m:1 Author::$books}
* @property Author|NULL $translator {m:1 Author::$translatedBooks}
* @property MHM|Tag[] $tags {m:m Tag::$books, isMain=true}
* @property Book|NULL $nextPart {1:1 Book::$previousPart, isMain=true}
* @property Book|NULL $previousPart {1:1 Book::$nextPart}
* @property Ean|NULL $ean {1:1 Ean::$book, isMain=true, cascade=[persist, remove]}
* @property Publisher $publisher {m:1 Publisher::$books}
* @property DateTimeImmutable $publishedAt {default now}
* @property NULL|DateTime $printedAt
* @property int $id {primary}
* @property string $title
* @property Author $author {m:1 Author::$books}
* @property Author|null $translator {m:1 Author::$translatedBooks}
* @property MHM|Tag[] $tags {m:m Tag::$books, isMain=true}
* @property Book|null $nextPart {1:1 Book::$previousPart, isMain=true}
* @property Book|null $previousPart {1:1 Book::$nextPart}
* @property Ean|null $ean {1:1 Ean::$book, isMain=true, cascade=[persist, remove]}
* @property Publisher $publisher {m:1 Publisher::$books}
* @property DateTimeImmutable $publishedAt {default now}
* @property DateTimeImmutable|null $printedAt
*/
final class Book extends Entity
{
Expand Down
9 changes: 5 additions & 4 deletions tests/inc/model/bookCollection/BookCollection.php
Expand Up @@ -2,20 +2,21 @@

/**
* This file is part of the Nextras\Orm library.
*
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace NextrasTests\Orm;

use DateTime;
use DateTimeImmutable;
use Nextras\Orm\Entity\Entity;


/**
* @property int $id {primary}
* @property string $name
* @property DateTime|NULL $updatedAt
* @property int $id {primary}
* @property string $name
* @property DateTimeImmutable|null $updatedAt
*/
class BookCollection extends Entity
{
Expand Down

0 comments on commit 7627376

Please sign in to comment.