@@ -68,21 +68,52 @@ private function loadFromFile(): void
6868 {
6969 $ contents = file_get_contents ($ this ->storagePath );
7070 if ($ contents === false || $ contents === '' ) {
71- throw new Exception ("Unserializable file content " );
71+ throw new Exception ("Empty rates cache file " );
7272 }
7373
74- $ data = @unserialize ($ contents , [
75- 'allowed_classes ' => [
76- Period::class,
77- DateTimeImmutable::class
78- ]
79- ]);
74+ $ data = json_decode ($ contents , true );
75+ if (!is_array ($ data )) {
76+ throw new Exception ("Malformed rates cache file " );
77+ }
78+
79+ $ rates = [];
80+ foreach ($ data as $ country => $ periods ) {
81+ if (!is_array ($ periods )) {
82+ throw new Exception ("Malformed rates cache file " );
83+ }
8084
81- if (false === is_array ($ data )) {
82- throw new Exception ("Unserializable file content " );
85+ foreach ($ periods as $ period ) {
86+ if (!is_array ($ period ) || !isset ($ period ['effective_from ' ], $ period ['rates ' ]) || !is_array ($ period ['rates ' ])) {
87+ throw new Exception ("Malformed rates cache file " );
88+ }
89+
90+ $ rates [$ country ][] = new Period (
91+ new DateTimeImmutable ($ period ['effective_from ' ]),
92+ $ period ['rates ' ]
93+ );
94+ }
95+ }
96+
97+ $ this ->rates = $ rates ;
98+ }
99+
100+ /**
101+ * @return array<string, array<int, array{effective_from: string, rates: array<string, float>}>>
102+ */
103+ private function ratesToArray (): array
104+ {
105+ $ out = [];
106+ foreach ($ this ->rates as $ country => $ periods ) {
107+ foreach ($ periods as $ period ) {
108+ /** @var Period $period */
109+ $ out [$ country ][] = [
110+ 'effective_from ' => $ period ->getEffectiveFrom ()->format (\DateTimeInterface::ATOM ),
111+ 'rates ' => $ period ->getRates (),
112+ ];
113+ }
83114 }
84115
85- $ this -> rates = $ data ;
116+ return $ out ;
86117 }
87118
88119 private function loadFromRemote (): void
@@ -101,15 +132,28 @@ private function loadFromRemote(): void
101132 }
102133
103134 // sort periods by DateTime so that later periods come first
104- foreach ($ this ->rates as $ country => $ periods ) {
135+ foreach (array_keys ( $ this ->rates ) as $ country ) {
105136 usort ($ this ->rates [$ country ], function (Period $ period1 , Period $ period2 ) {
106- return $ period1 ->getEffectiveFrom () > $ period2 ->getEffectiveFrom () ? - 1 : 1 ;
137+ return $ period2 ->getEffectiveFrom () <= > $ period1 ->getEffectiveFrom ();
107138 });
108139 }
109140
110141 // update local file with updated rates
111142 if ($ this ->storagePath !== '' ) {
112- file_put_contents ($ this ->storagePath , serialize ($ this ->rates ));
143+ $ payload = json_encode ($ this ->ratesToArray (), JSON_THROW_ON_ERROR );
144+ $ this ->writeStorageAtomic ($ payload );
145+ }
146+ }
147+
148+ private function writeStorageAtomic (string $ contents ): void
149+ {
150+ $ tmp = $ this ->storagePath . '. ' . bin2hex (random_bytes (6 )) . '.tmp ' ;
151+ if (file_put_contents ($ tmp , $ contents ) === false ) {
152+ return ;
153+ }
154+
155+ if (!@rename ($ tmp , $ this ->storagePath )) {
156+ @unlink ($ tmp );
113157 }
114158 }
115159
0 commit comments