1<?php 2/** 3 * This program is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation; either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along 14 * with this program; if not, write to the Free Software Foundation, Inc., 15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 * http://www.gnu.org/copyleft/gpl.html 17 * 18 * @file 19 */ 20 21namespace Wikimedia\DependencyStore; 22 23use BagOStuff; 24use InvalidArgumentException; 25 26/** 27 * Lightweight class for tracking path dependencies lists via an object cache instance 28 * 29 * This does not throw DependencyStoreException due to I/O errors since it is optimized for 30 * speed and availability. Read methods return empty placeholders on failure. Write methods 31 * might issue I/O in the background and return immediately. However, reads methods will at 32 * least block on the resolution (success/failure) of any such pending writes. 33 * 34 * @since 1.35 35 */ 36class KeyValueDependencyStore extends DependencyStore { 37 /** @var BagOStuff */ 38 private $stash; 39 40 /** 41 * @param BagOStuff $stash Storage backend 42 */ 43 public function __construct( BagOStuff $stash ) { 44 $this->stash = $stash; 45 } 46 47 public function retrieveMulti( $type, array $entities ) { 48 $entitiesByKey = []; 49 foreach ( $entities as $entity ) { 50 $entitiesByKey[$this->getStoreKey( $type, $entity )] = $entity; 51 } 52 53 $blobsByKey = $this->stash->getMulti( array_keys( $entitiesByKey ) ); 54 55 $results = []; 56 foreach ( $entitiesByKey as $key => $entity ) { 57 $blob = $blobsByKey[$key] ?? null; 58 $data = is_string( $blob ) ? json_decode( $blob, true ) : null; 59 $results[$entity] = $this->newEntityDependencies( 60 $data[self::KEY_PATHS] ?? [], 61 $data[self::KEY_AS_OF] ?? null 62 ); 63 } 64 65 return $results; 66 } 67 68 public function storeMulti( $type, array $dataByEntity, $ttl ) { 69 $blobsByKey = []; 70 foreach ( $dataByEntity as $entity => $data ) { 71 if ( !is_array( $data[self::KEY_PATHS] ) || !is_int( $data[self::KEY_AS_OF] ) ) { 72 throw new InvalidArgumentException( "Invalid entry for '$entity'" ); 73 } 74 75 // Normalize the list by removing duplicates and sorting 76 $data[self::KEY_PATHS] = array_values( array_unique( $data[self::KEY_PATHS] ) ); 77 sort( $data[self::KEY_PATHS], SORT_STRING ); 78 79 $blob = json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); 80 $blobsByKey[$this->getStoreKey( $type, $entity )] = $blob; 81 } 82 83 if ( $blobsByKey ) { 84 $this->stash->setMulti( $blobsByKey, $ttl, BagOStuff::WRITE_BACKGROUND ); 85 } 86 } 87 88 public function remove( $type, $entities ) { 89 $keys = []; 90 foreach ( (array)$entities as $entity ) { 91 $keys[] = $this->getStoreKey( $type, $entity ); 92 } 93 94 if ( $keys ) { 95 $this->stash->deleteMulti( $keys, BagOStuff::WRITE_BACKGROUND ); 96 } 97 } 98 99 public function renew( $type, $entities, $ttl ) { 100 $keys = []; 101 foreach ( (array)$entities as $entity ) { 102 $keys[] = $this->getStoreKey( $type, $entity ); 103 } 104 105 if ( $keys ) { 106 $this->stash->changeTTLMulti( $keys, $ttl, BagOStuff::WRITE_BACKGROUND ); 107 } 108 } 109 110 /** 111 * @param string $type 112 * @param string $entity 113 * @return string 114 */ 115 private function getStoreKey( $type, $entity ) { 116 return $this->stash->makeKey( "{$type}-dependencies", $entity ); 117 } 118} 119