@@ -8581,52 +8581,6 @@ public function purgeQueryCache(string $collection, ?string $namespace = null):
85818581 );
85828582 }
85838583
8584- /**
8585- * Restore a cached query document payload into database documents.
8586- *
8587- * Returns false when the cached payload is invalid or stale and should be
8588- * refreshed by the caller.
8589- *
8590- * @param Document $collection
8591- * @param mixed $payload
8592- * @return array<Document>|false
8593- * @throws AuthorizationException
8594- * @throws Exception
8595- */
8596- private function restoreQueryCacheDocuments (
8597- Document $ collection ,
8598- mixed $ payload ,
8599- ): array |false {
8600- if (!\is_array ($ payload )) {
8601- return false ;
8602- }
8603-
8604- $ documentSecurity = $ collection ->getAttribute ('documentSecurity ' , false );
8605- $ skipAuth = $ this ->authorization ->isValid (new Input (self ::PERMISSION_READ , $ collection ->getRead ()));
8606-
8607- if (!$ skipAuth && !$ documentSecurity && $ collection ->getId () !== self ::METADATA ) {
8608- throw new AuthorizationException ($ this ->authorization ->getDescription ());
8609- }
8610-
8611- $ documents = [];
8612- foreach ($ payload as $ document ) {
8613- if (!\is_array ($ document )) {
8614- return false ;
8615- }
8616-
8617- $ document = $ this ->createDocumentInstance ($ collection ->getId (), $ document );
8618- $ document = $ this ->casting ($ collection , $ document );
8619-
8620- if ($ this ->isTtlExpired ($ collection , $ document )) {
8621- return false ;
8622- }
8623-
8624- $ documents [] = $ document ;
8625- }
8626-
8627- return $ documents ;
8628- }
8629-
86308584 /**
86318585 * Execute a callback behind a cache-aside lookup.
86328586 *
@@ -8666,7 +8620,54 @@ public function withCache(
86668620 $ value = \is_array ($ cached ) && \array_key_exists ('value ' , $ cached ) ? $ cached ['value ' ] : false ;
86678621
86688622 if ($ value !== false ) {
8669- $ decoded = $ this ->decodeCacheValue ($ cached , $ value );
8623+ $ decoded = $ value ;
8624+ $ collection = $ cached ['collection ' ] ?? null ;
8625+
8626+ if (\is_string ($ collection ) && $ collection !== '' ) {
8627+ // Cached document payloads are stored as arrays; restore them
8628+ // to the same Document shape that find()/getDocument() return.
8629+ $ collection = $ this ->silent (fn () => $ this ->getCollection ($ collection ));
8630+
8631+ if ($ collection ->isEmpty ()) {
8632+ $ decoded = false ;
8633+ } else {
8634+ $ documentSecurity = $ collection ->getAttribute ('documentSecurity ' , false );
8635+ $ skipAuth = $ this ->authorization ->isValid (new Input (self ::PERMISSION_READ , $ collection ->getRead ()));
8636+
8637+ if (!$ skipAuth && !$ documentSecurity && $ collection ->getId () !== self ::METADATA ) {
8638+ throw new AuthorizationException ($ this ->authorization ->getDescription ());
8639+ }
8640+
8641+ $ payload = ($ cached ['type ' ] ?? null ) === 'document ' ? [$ value ] : $ value ;
8642+
8643+ if (!\is_array ($ payload )) {
8644+ $ decoded = false ;
8645+ } else {
8646+ $ documents = [];
8647+
8648+ foreach ($ payload as $ document ) {
8649+ if (!\is_array ($ document )) {
8650+ $ decoded = false ;
8651+ break ;
8652+ }
8653+
8654+ $ document = $ this ->createDocumentInstance ($ collection ->getId (), $ document );
8655+ $ document = $ this ->casting ($ collection , $ document );
8656+
8657+ if ($ this ->isTtlExpired ($ collection , $ document )) {
8658+ $ decoded = false ;
8659+ break ;
8660+ }
8661+
8662+ $ documents [] = $ document ;
8663+ }
8664+
8665+ if ($ decoded !== false ) {
8666+ $ decoded = ($ cached ['type ' ] ?? null ) === 'document ' ? ($ documents [0 ] ?? false ) : $ documents ;
8667+ }
8668+ }
8669+ }
8670+ }
86708671
86718672 if ($ decoded !== false ) {
86728673 return $ decoded ;
@@ -8688,134 +8689,95 @@ public function withCache(
86888689
86898690 if ($ value !== false ) {
86908691 try {
8691- $ encoded = $ this ->encodeCacheValue ($ value );
8692- if ($ encoded !== false ) {
8693- $ this ->cache ->save ($ key , $ encoded , $ hash );
8694- }
8695- } catch (Throwable $ e ) {
8696- Console::warning ('Warning: Failed to save cache value: ' . $ e ->getMessage ());
8697- }
8698- }
8699-
8700- return $ value ;
8701- }
8702-
8703- /**
8704- * @param array<string, mixed> $cached
8705- */
8706- private function decodeCacheValue (array $ cached , mixed $ value ): mixed
8707- {
8708- $ collection = $ cached ['collection ' ] ?? null ;
8709- if (!\is_string ($ collection ) || $ collection === '' ) {
8710- return $ value ;
8711- }
8712-
8713- $ collection = $ this ->silent (fn () => $ this ->getCollection ($ collection ));
8714- if ($ collection ->isEmpty ()) {
8715- return false ;
8716- }
8717-
8718- if (($ cached ['type ' ] ?? null ) === 'document ' ) {
8719- return $ this ->restoreQueryCacheDocument ($ collection , $ value );
8720- }
8721-
8722- return $ this ->restoreQueryCacheDocuments ($ collection , $ value );
8723- }
8724-
8725- private function restoreQueryCacheDocument (Document $ collection , mixed $ payload ): Document |false
8726- {
8727- $ documents = $ this ->restoreQueryCacheDocuments ($ collection , [$ payload ]);
8692+ $ encoded = false ;
87288693
8729- return $ documents === false ? false : ($ documents [0 ] ?? false );
8730- }
8731-
8732- /**
8733- * @return array<string, mixed>|false
8734- */
8735- private function encodeCacheValue (mixed $ value ): array |false
8736- {
8737- if ($ value instanceof Document) {
8738- $ collection = $ value ->getCollection ();
8739- if ($ collection === '' ) {
8740- return false ;
8741- }
8694+ if ($ value instanceof Document) {
8695+ $ collection = $ value ->getCollection ();
8696+
8697+ if ($ collection !== '' ) {
8698+ $ encoded = [
8699+ 'collection ' => $ collection ,
8700+ 'type ' => 'document ' ,
8701+ 'value ' => $ value ->getArrayCopy (),
8702+ ];
8703+ }
8704+ } elseif (!\is_array ($ value )) {
8705+ $ encoded = ['value ' => $ value ];
8706+ } else {
8707+ // Only homogeneous top-level document lists are safe to restore
8708+ // from cache. Mixed or nested Document payloads keep callback shape.
8709+ $ collection = null ;
8710+ $ hasDocuments = false ;
8711+ $ hasNonDocuments = false ;
8712+ $ cacheable = true ;
8713+ $ documents = [];
8714+ $ containsDocument = function (mixed $ item ) use (&$ containsDocument ): bool {
8715+ if ($ item instanceof Document) {
8716+ return true ;
8717+ }
87428718
8743- return [
8744- 'collection ' => $ collection ,
8745- 'type ' => 'document ' ,
8746- 'value ' => $ value ->getArrayCopy (),
8747- ];
8748- }
8719+ if (!\is_array ($ item )) {
8720+ return false ;
8721+ }
87498722
8750- $ collection = $ this -> getCacheValueCollection ( $ value );
8751- if ( $ collection === null || ! \is_array ( $ value )) {
8752- if ( $ this -> hasCacheValueDocument ( $ value )) {
8753- return false ;
8754- }
8723+ foreach ( $ item as $ child ) {
8724+ if ( $ containsDocument ( $ child )) {
8725+ return true ;
8726+ }
8727+ }
87558728
8756- return [ ' value ' => $ value ] ;
8757- }
8729+ return false ;
8730+ };
87588731
8759- return [
8760- ' collection ' => $ collection ,
8761- ' type ' => ' documents ' ,
8762- ' value ' => $ this -> encodeQueryCacheValue ( $ value ),
8763- ] ;
8764- }
8732+ foreach ( $ value as $ item ) {
8733+ if (! $ item instanceof Document) {
8734+ if ( $ hasDocuments || $ containsDocument ( $ item )) {
8735+ $ cacheable = false ;
8736+ break ;
8737+ }
87658738
8766- private function getCacheValueCollection (mixed $ value ): ?string
8767- {
8768- if ($ value instanceof Document) {
8769- return $ value ->getCollection () ?: null ;
8770- }
8739+ $ hasNonDocuments = true ;
8740+ continue ;
8741+ }
87718742
8772- if (!\is_array ($ value )) {
8773- return null ;
8774- }
8743+ if ($ hasNonDocuments ) {
8744+ $ cacheable = false ;
8745+ break ;
8746+ }
87758747
8776- foreach ($ value as $ item ) {
8777- $ collection = $ this ->getCacheValueCollection ($ item );
8778- if ($ collection !== null ) {
8779- return $ collection ;
8780- }
8781- }
8748+ $ documentCollection = $ item ->getCollection ();
8749+ if ($ documentCollection === '' ) {
8750+ $ cacheable = false ;
8751+ break ;
8752+ }
87828753
8783- return null ;
8784- }
8754+ if ($ collection !== null && $ collection !== $ documentCollection ) {
8755+ $ cacheable = false ;
8756+ break ;
8757+ }
87858758
8786- private function hasCacheValueDocument (mixed $ value ): bool
8787- {
8788- if ($ value instanceof Document) {
8789- return true ;
8790- }
8759+ $ collection = $ documentCollection ;
8760+ $ hasDocuments = true ;
8761+ $ documents [] = $ item ->getArrayCopy ();
8762+ }
87918763
8792- if (!\is_array ($ value )) {
8793- return false ;
8794- }
8764+ if ($ cacheable ) {
8765+ $ encoded = $ hasDocuments ? [
8766+ 'collection ' => $ collection ,
8767+ 'type ' => 'documents ' ,
8768+ 'value ' => $ documents ,
8769+ ] : ['value ' => $ value ];
8770+ }
8771+ }
87958772
8796- foreach ($ value as $ item ) {
8797- if ($ this ->hasCacheValueDocument ($ item )) {
8798- return true ;
8773+ if ($ encoded !== false ) {
8774+ $ this ->cache ->save ($ key , $ encoded , $ hash );
8775+ }
8776+ } catch (Throwable $ e ) {
8777+ Console::warning ('Warning: Failed to save cache value: ' . $ e ->getMessage ());
87998778 }
88008779 }
88018780
8802- return false ;
8803- }
8804-
8805- private function encodeQueryCacheValue (mixed $ value ): mixed
8806- {
8807- if ($ value instanceof Document) {
8808- return $ value ->getArrayCopy ();
8809- }
8810-
8811- if (!\is_array ($ value )) {
8812- return $ value ;
8813- }
8814-
8815- foreach ($ value as $ key => $ item ) {
8816- $ value [$ key ] = $ this ->encodeQueryCacheValue ($ item );
8817- }
8818-
88198781 return $ value ;
88208782 }
88218783
@@ -9777,9 +9739,20 @@ public function getQueryCacheField(
97779739 'filters ' => $ this ->getActiveFilterSignatures (),
97789740 ];
97799741
9742+ $ schemaHash = '' ;
9743+ if ($ collection !== null && !$ collection ->isEmpty ()) {
9744+ // Schema-affecting changes must move callers onto a fresh cache field.
9745+ $ schemaHash = \md5 (
9746+ \json_encode ($ collection ->getAttribute ('attributes ' , []))
9747+ . \json_encode ($ collection ->getAttribute ('indexes ' , []))
9748+ . \json_encode ($ collection ->getAttribute ('$permissions ' , []))
9749+ . \json_encode ($ collection ->getAttribute ('documentSecurity ' , false ))
9750+ );
9751+ }
9752+
97809753 return \sprintf (
97819754 '%s:%s:%s ' ,
9782- $ this -> getQueryCacheSchemaHash ( $ collection ) ,
9755+ $ schemaHash ,
97839756 \md5 (\json_encode ($ queryPayload ) ?: '' ),
97849757 $ field ,
97859758 );
@@ -9830,20 +9803,6 @@ private function normalizeQueryCacheQueryValue(mixed $value): mixed
98309803 return $ value ;
98319804 }
98329805
9833- private function getQueryCacheSchemaHash (?Document $ collection ): string
9834- {
9835- if ($ collection === null || $ collection ->isEmpty ()) {
9836- return '' ;
9837- }
9838-
9839- return \md5 (
9840- \json_encode ($ collection ->getAttribute ('attributes ' , []))
9841- . \json_encode ($ collection ->getAttribute ('indexes ' , []))
9842- . \json_encode ($ collection ->getAttribute ('$permissions ' , []))
9843- . \json_encode ($ collection ->getAttribute ('documentSecurity ' , false ))
9844- );
9845- }
9846-
98479806 /**
98489807 * @return array<string, string>
98499808 */
0 commit comments