Skip to content

Commit 98f5ad5

Browse files
alexandre-dauboisfabpot
authored andcommitted
Fix unbounded memoisation of IntlDateFormatter / NumberFormatter
1 parent 32f15a3 commit 98f5ad5

2 files changed

Lines changed: 39 additions & 0 deletions

File tree

IntlExtension.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ private static function availableDateFormats(): array
145145
'monetary_grouping_separator' => \NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL,
146146
];
147147

148+
private const MAX_CACHED_FORMATTERS = 100;
149+
148150
private $dateFormatters = [];
149151
private $numberFormatters = [];
150152
private $dateFormatterPrototype;
@@ -441,6 +443,9 @@ private function createDateFormatter(?string $locale, ?string $dateFormat, ?stri
441443
$hash = $locale.'|'.$dateFormatValue.'|'.$timeFormatValue.'|'.$timezoneName.'|'.$calendar.'|'.$pattern;
442444

443445
if (!isset($this->dateFormatters[$hash])) {
446+
if (\count($this->dateFormatters) >= self::MAX_CACHED_FORMATTERS) {
447+
array_shift($this->dateFormatters);
448+
}
444449
$this->dateFormatters[$hash] = new \IntlDateFormatter($locale, $dateFormatValue, $timeFormatValue, $timezone, $calendar, $pattern);
445450
}
446451

@@ -487,6 +492,9 @@ private function createNumberFormatter(?string $locale, string $style, array $at
487492
$hash = $locale.'|'.$style.'|'.json_encode($attrs).'|'.json_encode($textAttrs).'|'.json_encode($symbols);
488493

489494
if (!isset($this->numberFormatters[$hash])) {
495+
if (\count($this->numberFormatters) >= self::MAX_CACHED_FORMATTERS) {
496+
array_shift($this->numberFormatters);
497+
}
490498
$this->numberFormatters[$hash] = new \NumberFormatter($locale, self::NUMBER_STYLES[$style]);
491499
}
492500

Tests/IntlExtensionTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,35 @@ public function testFormatterOverridenProto()
9696
$ext->formatDateTime($env, new \DateTime('2020-02-20T13:37:00+00:00'), 'short', 'short', 'yyyy-MM-dd HH:mm:ss', 'UTC', 'gregorian', 'en_US')
9797
);
9898
}
99+
100+
public function testDateFormatterCacheIsBounded()
101+
{
102+
$ext = new IntlExtension();
103+
$env = new Environment(new ArrayLoader());
104+
$date = new \DateTime('2020-02-20T13:37:00+00:00');
105+
106+
for ($i = 0; $i < 250; ++$i) {
107+
$ext->formatDateTime($env, $date, 'medium', 'medium', 'yyyy-MM-dd-'.$i, 'UTC', 'gregorian', 'en_US');
108+
}
109+
110+
$cache = (new \ReflectionProperty(IntlExtension::class, 'dateFormatters'))->getValue($ext);
111+
$this->assertLessThanOrEqual(100, \count($cache));
112+
$this->assertSame(
113+
'2020-02-20-249',
114+
$ext->formatDateTime($env, $date, 'medium', 'medium', 'yyyy-MM-dd-249', 'UTC', 'gregorian', 'en_US')
115+
);
116+
}
117+
118+
public function testNumberFormatterCacheIsBounded()
119+
{
120+
$ext = new IntlExtension();
121+
122+
for ($i = 0; $i < 250; ++$i) {
123+
$ext->formatNumber(1, ['multiplier' => $i + 1], 'decimal', 'default', 'en_US');
124+
}
125+
126+
$cache = (new \ReflectionProperty(IntlExtension::class, 'numberFormatters'))->getValue($ext);
127+
$this->assertLessThanOrEqual(100, \count($cache));
128+
$this->assertGreaterThan(1, \count($cache));
129+
}
99130
}

0 commit comments

Comments
 (0)