wikimedia/mediawiki-extensions-Wikibase

View on GitHub
lib/includes/Store/CachingPropertyInfoLookup.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace Wikibase\Lib\Store;

use MediaWiki\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;
use Wikibase\DataModel\Entity\PropertyId;
use Wikimedia\ObjectCache\BagOStuff;
use Wikimedia\ObjectCache\WANObjectCache;

/**
 * Class CachingPropertyInfoLookup is an implementation of PropertyInfoLookup
 * that maintains a cached copy of the property info.
 * Note: Cache keys used by this class should be in sync with keys used by
 * CacheAwarePropertyInfoStore instance.
 *
 * @license GPL-2.0-or-later
 * @author Daniel Kinzler
 */
class CachingPropertyInfoLookup implements PropertyInfoLookup {

    /**
     * @var PropertyInfoLookup
     */
    protected $lookup;

    /**
     * @var BagOStuff|WANObjectCache
     */
    protected $cache;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var int
     */
    protected $cacheDuration;

    /**
     * @var string
     */
    protected $cacheKeyGroup;

    /**
     * Maps properties to info arrays
     *
     * @var array[]
     */
    protected $propertyInfo = [];

    /**
     * @var bool Is the propertyInfo fully populated?
     */
    protected $propertyInfoFullyPopulated = false;

    /**
     * @param PropertyInfoLookup $lookup The info lookup to call back to.
     * @param BagOStuff|WANObjectCache $cache
     * @param string $cacheKeyGroup Group name of the Wikibases to be used when generating global cache keys
     * @param int $cacheDuration Number of seconds to keep the cached version for.
     *                                   Defaults to 3600 seconds = 1 hour.
     */
    public function __construct(
        PropertyInfoLookup $lookup,
        $cache,
        $cacheKeyGroup,
        $cacheDuration = 3600
    ) {
        $this->lookup = $lookup;
        $this->cache = $cache;
        $this->cacheDuration = $cacheDuration;

        if ( $cacheKeyGroup === '' ) {
            throw new \InvalidArgumentException( '$cacheKeyGroup should be specified' );
        }

        $this->cacheKeyGroup = $cacheKeyGroup;
        // TODO: Inject
        $this->logger = LoggerFactory::getInstance( 'Wikibase' );
    }

    /**
     * @see PropertyInfoLookup::getPropertyInfo
     *
     * @param PropertyId $propertyId
     *
     * @return array|null
     */
    public function getPropertyInfo( PropertyId $propertyId ) {
        $id = $propertyId->getSerialization();

        if ( isset( $this->propertyInfo[$id] ) ) {
            $this->logger->debug(
                '{method}: using in class cached property info table', [ 'method' => __METHOD__ ]
            );
            return $this->propertyInfo[$id];
        }

        if ( $this->propertyInfoFullyPopulated ) {
            $this->logger->debug(
                '{method}: using in class cached property info table', [ 'method' => __METHOD__ ]
            );
            return null;
        }

        $info = $this->getPropertyInfoFromCache( $propertyId );
        $this->propertyInfo[$id] = $info;
        return $info;
    }

    /**
     * @param PropertyId $propertyId
     *
     * @return array|null
     */
    private function getPropertyInfoFromCache( PropertyId $propertyId ) {
        $info = $this->cache->getWithSetCallback(
            $this->getSinglePropertyCacheKey( $propertyId ),
            $this->cacheDuration,
            function () use ( $propertyId ) {
                $allInfo = $this->getAllPropertyInfo();
                $id = $propertyId->getSerialization();

                // If there is no property, return false to not cache
                return $allInfo[$id] ?? false;
            }
        );

        return $info ?: null;
    }

    /**
     * @see PropertyInfoLookup::getPropertyInfoForDataType
     *
     * @param string $dataType
     *
     * @return array[] Array containing serialized property IDs as keys and info arrays as values
     */
    public function getPropertyInfoForDataType( $dataType ) {
        $propertyInfo = $this->getAllPropertyInfo();
        $propertyInfoForDataType = [];

        foreach ( $propertyInfo as $id => $info ) {
            if ( $info[PropertyInfoLookup::KEY_DATA_TYPE] === $dataType ) {
                $propertyInfoForDataType[$id] = $info;
            }
        }

        return $propertyInfoForDataType;
    }

    /**
     * @see PropertyInfoLookup::getAllPropertyInfo
     *
     * @return array[] Array containing serialized property IDs as keys and info arrays as values
     */
    public function getAllPropertyInfo() {
        if ( !$this->propertyInfoFullyPopulated ) {
            $wanCacheHit = true;
            $fname = __METHOD__;
            $this->propertyInfo = $this->cache->getWithSetCallback(
                $this->getFullTableCacheKey(),
                $this->cacheDuration,
                function () use ( &$wanCacheHit, $fname ) {
                    $this->logger->debug(
                        '{method}: caching fresh property info table', [ 'method' => $fname ]
                    );
                    $wanCacheHit = false;
                    return $this->lookup->getAllPropertyInfo();
                }
            );
            if ( !$wanCacheHit ) {
                $this->logger->debug(
                    '{method}: Repopulating property info table in WAN cache', [ 'method' => __METHOD__ ]
                );
            }
            $this->propertyInfoFullyPopulated = true;
        }

        return $this->propertyInfo;
    }

    private function getFullTableCacheKey() {
        return $this->cache->makeGlobalKey(
            CacheAwarePropertyInfoStore::CACHE_CLASS,
            $this->cacheKeyGroup
        );
    }

    private function getSinglePropertyCacheKey( PropertyId $propertyId ) {
        return $this->cache->makeGlobalKey(
            CacheAwarePropertyInfoStore::CACHE_CLASS,
            $this->cacheKeyGroup,
            $propertyId->getSerialization()
        );
    }

}