Skip to content

Commit 8e66b55

Browse files
authored
fix timestampable use of datetimeclass (#2017)
* fix timestampablebehavior not using datetimeclass * assert behavior microtimes are equal on creation * remove unused variables * assert dateTimeClass configuration is used in behavior
1 parent 6719f24 commit 8e66b55

File tree

5 files changed

+108
-18
lines changed

5 files changed

+108
-18
lines changed

src/Propel/Generator/Behavior/Timestampable/TimestampableBehavior.php

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
namespace Propel\Generator\Behavior\Timestampable;
1010

11+
use DateTime;
1112
use Propel\Generator\Builder\Om\AbstractOMBuilder;
13+
use Propel\Generator\Builder\Om\ObjectBuilder;
1214
use Propel\Generator\Model\Behavior;
1315

1416
/**
@@ -101,9 +103,17 @@ protected function getColumnConstant(string $columnName, AbstractOMBuilder $buil
101103
public function preUpdate(AbstractOMBuilder $builder): string
102104
{
103105
if ($this->withUpdatedAt()) {
104-
$valueSource = strtoupper($this->getTable()->getColumn($this->getParameter('update_column'))->getType()) === 'INTEGER'
106+
$updateColumn = $this->getTable()->getColumn($this->getParameter('update_column'));
107+
108+
$dateTimeClass = DateTime::class;
109+
110+
if ($builder instanceof ObjectBuilder) {
111+
$dateTimeClass = $builder->getDateTimeClass($updateColumn);
112+
}
113+
114+
$valueSource = strtoupper($updateColumn->getType()) === 'INTEGER'
105115
? 'time()'
106-
: '\\Propel\\Runtime\\Util\\PropelDateTime::createHighPrecision()';
116+
: "PropelDateTime::createHighPrecision(null, '$dateTimeClass')";
107117

108118
return 'if ($this->isModified() && !$this->isColumnModified(' . $this->getColumnConstant('update_column', $builder) . ")) {
109119
\$this->" . $this->getColumnSetter('update_column') . "({$valueSource});
@@ -123,22 +133,44 @@ public function preUpdate(AbstractOMBuilder $builder): string
123133
public function preInsert(AbstractOMBuilder $builder): string
124134
{
125135
$script = '$time = time();
126-
$highPrecision = \\Propel\\Runtime\\Util\\PropelDateTime::createHighPrecision();';
136+
$mtime = PropelDateTime::formatMicrotime(microtime(true));';
127137

128138
if ($this->withCreatedAt()) {
129-
$valueSource = strtoupper($this->getTable()->getColumn($this->getParameter('create_column'))->getType()) === 'INTEGER'
139+
$createColumn = $this->getTable()->getColumn($this->getParameter('create_column'));
140+
141+
$dateTimeClass = DateTime::class;
142+
143+
if ($builder instanceof ObjectBuilder) {
144+
$dateTimeClass = $builder->getDateTimeClass($createColumn);
145+
}
146+
147+
$script .= "
148+
\$highPrecisionCreate = PropelDateTime::createHighPrecision(\$mtime, '$dateTimeClass');";
149+
150+
$valueSource = strtoupper($createColumn->getType()) === 'INTEGER'
130151
? '$time'
131-
: '$highPrecision';
152+
: '$highPrecisionCreate';
132153
$script .= "
133154
if (!\$this->isColumnModified(" . $this->getColumnConstant('create_column', $builder) . ")) {
134155
\$this->" . $this->getColumnSetter('create_column') . "({$valueSource});
135156
}";
136157
}
137158

138159
if ($this->withUpdatedAt()) {
139-
$valueSource = strtoupper($this->getTable()->getColumn($this->getParameter('update_column'))->getType()) === 'INTEGER'
160+
$updateColumn = $this->getTable()->getColumn($this->getParameter('update_column'));
161+
162+
$dateTimeClass = DateTime::class;
163+
164+
if ($builder instanceof ObjectBuilder) {
165+
$dateTimeClass = $builder->getDateTimeClass($updateColumn);
166+
}
167+
168+
$script .= "
169+
\$highPrecisionUpdate = PropelDateTime::createHighPrecision(\$mtime, '$dateTimeClass');";
170+
171+
$valueSource = strtoupper($updateColumn->getType()) === 'INTEGER'
140172
? '$time'
141-
: '$highPrecision';
173+
: '$highPrecisionUpdate';
142174
$script .= "
143175
if (!\$this->isColumnModified(" . $this->getColumnConstant('update_column', $builder) . ")) {
144176
\$this->" . $this->getColumnSetter('update_column') . "({$valueSource});

src/Propel/Generator/Builder/Om/ObjectBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7360,7 +7360,7 @@ protected function addMagicCall(string &$script): void
73607360
*
73617361
* @return string
73627362
*/
7363-
protected function getDateTimeClass(Column $column): string
7363+
public function getDateTimeClass(Column $column): string
73647364
{
73657365
if (PropelTypes::isPhpObjectType($column->getPhpType())) {
73667366
return $column->getPhpType();

src/Propel/Runtime/Util/PropelDateTime.php

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace Propel\Runtime\Util;
1010

1111
use DateTime;
12+
use DateTimeImmutable;
1213
use DateTimeInterface;
1314
use DateTimeZone;
1415
use Exception;
@@ -70,34 +71,53 @@ protected static function isTimestamp($value): bool
7071
* Usually `new \Datetime()` does not contain milliseconds so you need a method like this.
7172
*
7273
* @param string|null $time Optional, in seconds. Floating point allowed.
74+
* @param string $dateTimeClass Optional, class name of the created object.
7375
*
7476
* @throws \InvalidArgumentException
7577
*
76-
* @return \DateTime
78+
* @return \DateTimeInterface An instance of $dateTimeClass
7779
*/
78-
public static function createHighPrecision(?string $time = null): DateTime
80+
public static function createHighPrecision(?string $time = null, string $dateTimeClass = 'DateTime'): DateTimeInterface
7981
{
80-
$dateTime = DateTime::createFromFormat('U.u', $time ?: self::getMicrotime());
82+
$allowStringIsA = true;
83+
84+
if (!is_a($dateTimeClass, DateTime::class, $allowStringIsA) && !is_a($dateTimeClass, DateTimeImmutable::class, $allowStringIsA)) {
85+
throw new InvalidArgumentException('`' . $dateTimeClass . '` needs to be an instance of DateTime or DateTimeImmutable');
86+
}
87+
88+
$dateTime = $dateTimeClass::createFromFormat('U.u', $time ?: self::getMicrotime());
8189
if ($dateTime === false) {
8290
throw new InvalidArgumentException('Cannot create a datetime object from `' . $time . '`');
8391
}
8492

85-
$dateTime->setTimeZone(new DateTimeZone(date_default_timezone_get()));
93+
$dateTime = $dateTime->setTimeZone(new DateTimeZone(date_default_timezone_get()));
8694

8795
return $dateTime;
8896
}
8997

9098
/**
91-
* Get the current microtime with milliseconds. Making sure that the decimal point separator is always ".", ignoring
99+
* Format the output of microtime(true) making sure that the decimal point separator is always ".", ignoring
92100
* what is set with the current locale. Otherwise, self::createHighPrecision would return false.
93101
*
102+
* @param float $mtime Time in milliseconds.
103+
*
104+
* @return string
105+
*/
106+
public static function formatMicrotime(float $mtime): string
107+
{
108+
return number_format($mtime, 6, '.', '');
109+
}
110+
111+
/**
112+
* Get the current microtime with milliseconds.
113+
*
94114
* @return string
95115
*/
96116
public static function getMicrotime(): string
97117
{
98118
$mtime = microtime(true);
99119

100-
return number_format($mtime, 6, '.', '');
120+
return self::formatMicrotime($mtime);
101121
}
102122

103123
/**

tests/Propel/Tests/Generator/Behavior/Timestampable/TimestampableBehaviorTest.php

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Propel\Tests\Generator\Behavior\Timestampable;
1010

11+
use Propel\Generator\Config\QuickGeneratorConfig;
1112
use Propel\Generator\Util\QuickBuilder;
1213
use Propel\Runtime\Collection\ObjectCollection;
1314
use Propel\Tests\Bookstore\Behavior\Map\Table1TableMap;
@@ -17,6 +18,7 @@
1718
use Propel\Tests\Helpers\Bookstore\BookstoreTestBase;
1819
use TableWithoutCreatedAt;
1920
use TableWithoutUpdatedAt;
21+
use TableDateTimeClass;
2022

2123
/**
2224
* Tests for TimestampableBehavior class
@@ -95,7 +97,6 @@ public function testPreSaveNoChange()
9597
$t1->save();
9698
$this->assertTimeEquals($tsave, $t1->getUpdatedAt('U'), 'Timestampable sets updated_column to time() on creation');
9799
sleep(1);
98-
$tupdate = time();
99100
$t1->save();
100101
$this->assertTimeEquals($tsave, $t1->getUpdatedAt('U'), 'Timestampable only changes updated_column if the object was modified');
101102
}
@@ -129,9 +130,9 @@ public function testPreInsert()
129130
$tsave = time();
130131
$t1->save();
131132
$this->assertTimeEquals($tsave, $t1->getCreatedAt('U'), 'Timestampable sets created_column to time() on creation');
133+
$this->assertEquals($t1->getCreatedAt('u'), $t1->getUpdatedAt('u'), 'Timestampable does not set created_column and updated_column to the same value on creation');
132134
sleep(1);
133135
$t1->setTitle('foo');
134-
$tupdate = time();
135136
$t1->save();
136137
$this->assertTimeEquals($tsave, $t1->getCreatedAt('U'), 'Timestampable does not update created_column on update');
137138
}
@@ -356,4 +357,40 @@ public function testDisableCreatedAt()
356357
$this->assertEquals(1, $obj->save());
357358
$this->assertNotNull($obj->getUpdatedAt());
358359
}
360+
361+
/**
362+
* @return void
363+
*/
364+
public function testDateTimeClass()
365+
{
366+
$schema = <<<EOF
367+
<database name="timestampable_database">
368+
<table name="table_date_time_class">
369+
<column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
370+
<behavior name="timestampable">
371+
</behavior>
372+
</table>
373+
</database>
374+
EOF;
375+
376+
// Custom Configuration to use DateTimeImmutable
377+
$builder = new QuickBuilder();
378+
$config = new QuickGeneratorConfig([
379+
'propel' => [
380+
'generator' => [
381+
'dateTime' => [
382+
'dateTimeClass' => 'DateTimeImmutable',
383+
],
384+
],
385+
],
386+
]);
387+
$builder->setSchema($schema);
388+
$builder->setConfig($config);
389+
$builder->build();
390+
391+
$obj = new TableDateTimeClass();
392+
$obj->save();
393+
$this->assertInstanceOf('DateTimeImmutable', $obj->getCreatedAt(), 'Timestampable behavior does not use the propel.generator.dateTime.dateTimeClass configuration property for created_column');
394+
$this->assertInstanceOf('DateTimeImmutable', $obj->getUpdatedAt(), 'Timestampable behavior does not use the propel.generator.dateTime.dateTimeClass configuration property for updated_column');
395+
}
359396
}

tests/Propel/Tests/Runtime/Util/PropelDateTimeTest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use DateTime;
1212
use DateTimeImmutable;
13+
use DateTimeInterface;
1314
use DateTimeZone;
1415
use PHPUnit\Framework\TestCase;
1516
use Propel\Runtime\Exception\PropelException;
@@ -247,11 +248,11 @@ public function testIsTimestamp()
247248
public function testCreateHighPrecision()
248249
{
249250
$createHP = PropelDateTime::createHighPrecision();
250-
$this->assertInstanceOf(DateTime::class, $createHP);
251+
$this->assertInstanceOf(DateTimeInterface::class, $createHP);
251252

252253
setlocale(LC_ALL, 'de_DE.UTF-8');
253254
$createHP = PropelDateTime::createHighPrecision();
254-
$this->assertInstanceOf(DateTime::class, $createHP);
255+
$this->assertInstanceOf(DateTimeInterface::class, $createHP);
255256
}
256257

257258
/**

0 commit comments

Comments
 (0)