@@ -24,18 +24,13 @@ final class ReflectionCache
2424 private $ records = [];
2525
2626 /**
27- * @var bool
27+ * @var array<string, array{error?: ?Error, result?: array<QueryReflector::FETCH_TYPE*, ?Type>}>
2828 */
29- private $ changed = false ;
29+ private $ changes = [] ;
3030
3131 private function __construct (string $ cacheFile )
3232 {
3333 $ this ->cacheFile = $ cacheFile ;
34-
35- if (!is_file ($ cacheFile )) {
36- // enforce file creation
37- $ this ->changed = true ;
38- }
3934 }
4035
4136 public static function create (string $ cacheFile ): self
@@ -49,35 +44,69 @@ public static function load(string $cacheFile): self
4944 throw new DbaException (sprintf ('Cache file "%s" is not readable ' , $ cacheFile ));
5045 }
5146
52- $ cache = require $ cacheFile ;
53-
5447 $ reflectionCache = new self ($ cacheFile );
55- if (\is_array ($ cache ) && \array_key_exists ('schemaVersion ' , $ cache ) && self ::SCHEMA_VERSION === $ cache ['schemaVersion ' ]) {
56- $ reflectionCache ->records = $ cache ['records ' ];
48+ $ cachedRecords = $ reflectionCache ->readCache ();
49+ if (null !== $ cachedRecords ) {
50+ $ reflectionCache ->records = $ cachedRecords ;
5751 }
5852
5953 return $ reflectionCache ;
6054 }
6155
56+ /**
57+ * @return array<string, array{error?: ?Error, result?: array<QueryReflector::FETCH_TYPE*, ?Type>}>|null
58+ */
59+ private function readCache (): ?array
60+ {
61+ clearstatcache (true , $ this ->cacheFile );
62+ $ cache = require $ this ->cacheFile ;
63+
64+ if (\is_array ($ cache ) && \array_key_exists ('schemaVersion ' , $ cache ) && self ::SCHEMA_VERSION === $ cache ['schemaVersion ' ]) {
65+ return $ cache ['records ' ];
66+ }
67+
68+ return null ;
69+ }
70+
6271 public function persist (): void
6372 {
64- if (! $ this ->changed ) {
73+ if (0 === \count ( $ this ->changes ) ) {
6574 return ;
6675 }
6776
68- $ records = $ this ->records ;
77+ // freshly read the cache as it might have changed in the meantime
78+ $ cachedRecords = $ this ->readCache ();
79+
80+ // actually we should lock even earlier, but we could no longer read the cache-file with require()
81+ $ handle = fopen ($ this ->cacheFile , 'w+ ' );
82+ if (false === $ handle ) {
83+ throw new DbaException (sprintf ('Could not open cache file "%s" for writing ' , $ this ->cacheFile ));
84+ }
85+ flock ($ handle , LOCK_EX );
86+
87+ // re-apply all changes to the current cache-state
88+ if (null === $ cachedRecords ) {
89+ $ newRecords = $ this ->changes ;
90+ } else {
91+ $ newRecords = array_merge ($ cachedRecords , $ this ->changes );
92+ }
6993
7094 // sort records to prevent unnecessary cache invalidation caused by different order of queries
71- uksort ($ records , function ($ queryA , $ queryB ) {
95+ uksort ($ newRecords , function ($ queryA , $ queryB ) {
7296 return $ queryA <=> $ queryB ;
7397 });
7498
75- if (false === file_put_contents ($ this ->cacheFile , '<?php return ' .var_export ([
76- 'schemaVersion ' => self ::SCHEMA_VERSION ,
77- 'records ' => $ records ,
78- ], true ).'; ' , LOCK_EX )) {
99+ $ cacheContent = '<?php return ' .var_export ([
100+ 'schemaVersion ' => self ::SCHEMA_VERSION ,
101+ 'records ' => $ newRecords ,
102+ ], true ).'; ' ;
103+
104+ if (false === fwrite ($ handle , $ cacheContent )) {
79105 throw new DbaException (sprintf ('Unable to write cache file "%s" ' , $ this ->cacheFile ));
80106 }
107+
108+ // frees the lock implictly
109+ fclose ($ handle );
81110 }
82111
83112 public function hasValidationError (string $ queryString ): bool
@@ -108,14 +137,11 @@ public function getValidationError(string $queryString): ?Error
108137 public function putValidationError (string $ queryString , ?Error $ error ): void
109138 {
110139 if (!\array_key_exists ($ queryString , $ this ->records )) {
111- $ this ->records [$ queryString ] = [];
112- $ this ->changed = true ;
140+ $ this ->changes [$ queryString ] = $ this ->records [$ queryString ] = [];
113141 }
114142
115- $ cacheEntry = &$ this ->records [$ queryString ];
116- if (!\array_key_exists ('error ' , $ cacheEntry ) || $ cacheEntry ['error ' ] !== $ error ) {
117- $ cacheEntry ['error ' ] = $ error ;
118- $ this ->changed = true ;
143+ if (!\array_key_exists ('error ' , $ this ->records [$ queryString ]) || $ this ->records [$ queryString ]['error ' ] !== $ error ) {
144+ $ this ->changes [$ queryString ]['error ' ] = $ this ->records [$ queryString ]['error ' ] = $ error ;
119145 }
120146 }
121147
@@ -163,19 +189,16 @@ public function getResultType(string $queryString, int $fetchType): ?Type
163189 public function putResultType (string $ queryString , int $ fetchType , ?Type $ resultType ): void
164190 {
165191 if (!\array_key_exists ($ queryString , $ this ->records )) {
166- $ this ->records [$ queryString ] = [];
167- $ this ->changed = true ;
192+ $ this ->changes [$ queryString ] = $ this ->records [$ queryString ] = [];
168193 }
169194
170- $ cacheEntry = &$ this ->records [$ queryString ];
171- if (!\array_key_exists ('result ' , $ cacheEntry )) {
172- $ cacheEntry ['result ' ] = [];
173- $ this ->changed = true ;
195+ if (!\array_key_exists ('result ' , $ this ->records [$ queryString ])) {
196+ $ this ->changes [$ queryString ]['result ' ] = $ this ->records [$ queryString ]['result ' ] = [];
174197 }
175198
176- if (! \array_key_exists ( $ fetchType , $ cacheEntry [ ' result ' ]) || $ cacheEntry [ ' result ' ][ $ fetchType ] !== $ resultType ) {
177- $ cacheEntry [ 'result ' ][$ fetchType ] = $ resultType;
178- $ this ->changed = true ;
199+ // @phpstan-ignore-next-line
200+ if (! \array_key_exists ( $ fetchType , $ this -> records [ $ queryString ][ ' result ' ]) || $ this -> records [ $ queryString ][ 'result ' ][$ fetchType ] !== $ resultType) {
201+ $ this ->changes [ $ queryString ][ ' result ' ][ $ fetchType ] = $ this -> records [ $ queryString ][ ' result ' ][ $ fetchType ] = $ resultType ;
179202 }
180203 }
181204}
0 commit comments