Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
CacheBackedImageRecommendationProvider
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 4
42
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getWithSetCallback
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 makeKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments\NewcomerTasks\AddImage;
4
5use GrowthExperiments\NewcomerTasks\TaskType\ImageRecommendationBaseTaskType;
6use GrowthExperiments\NewcomerTasks\TaskType\TaskType;
7use MediaWiki\Linker\LinkTarget;
8use StatusValue;
9use Wikimedia\Assert\Assert;
10use Wikimedia\ObjectCache\WANObjectCache;
11use Wikimedia\Stats\IBufferingStatsdDataFactory;
12
13/**
14 * Get image recommendation data from cache if possible; fall back to service provider otherwise.
15 *
16 * Used as a drop-in replacement for the uncached ServiceImageRecommendationProvider. The static
17 * method is called in CacheDecorator.php when a TaskSet includes ImageRecommendationTaskType objects.
18 */
19class CacheBackedImageRecommendationProvider implements ImageRecommendationProvider {
20
21    /** @var WANObjectCache */
22    private $cache;
23
24    /** @var ImageRecommendationProvider */
25    private $imageRecommendationProvider;
26
27    /** @var IBufferingStatsdDataFactory */
28    private $statsdDataFactory;
29
30    /**
31     * @param WANObjectCache $cache
32     * @param ImageRecommendationProvider $imageRecommendationProvider
33     * @param IBufferingStatsdDataFactory $statsdDataFactory
34     */
35    public function __construct(
36        WANObjectCache $cache,
37        ImageRecommendationProvider $imageRecommendationProvider,
38        IBufferingStatsdDataFactory $statsdDataFactory
39    ) {
40        $this->cache = $cache;
41        $this->imageRecommendationProvider = $imageRecommendationProvider;
42        $this->statsdDataFactory = $statsdDataFactory;
43    }
44
45    /** @inheritDoc */
46    public function get( LinkTarget $title, TaskType $taskType ) {
47        Assert::parameterType( ImageRecommendationBaseTaskType::class, $taskType, '$taskType' );
48        return self::getWithSetCallback(
49            $this->cache,
50            $this->imageRecommendationProvider,
51            $taskType,
52            $title,
53            __METHOD__,
54            $this->statsdDataFactory
55        );
56    }
57
58    /**
59     * @param WANObjectCache $cache
60     * @param ImageRecommendationProvider $imageRecommendationProvider
61     * @param TaskType $taskType
62     * @param LinkTarget $title
63     * @param string $fname The context in which this method is called. Used for instrumenting cache misses.
64     * @param IBufferingStatsdDataFactory $statsdDataFactory
65     * @return false|mixed
66     */
67    public static function getWithSetCallback(
68        WANObjectCache $cache,
69        ImageRecommendationProvider $imageRecommendationProvider,
70        TaskType $taskType,
71        LinkTarget $title,
72        string $fname,
73        IBufferingStatsdDataFactory $statsdDataFactory
74    ) {
75        return $cache->getWithSetCallback(
76            self::makeKey( $cache, $taskType->getId(), $title->getDBkey() ),
77            // The recommendation won't change, but other metadata might and caching for longer might be
78            // problematic if e.g. the image got vandalized.
79            $cache::TTL_MINUTE * 5,
80            static function ( $oldValue, &$ttl ) use (
81                $title, $taskType, $imageRecommendationProvider, $cache, $fname, $statsdDataFactory
82            ) {
83                // This is a cache miss. That is expected when TaskSetListener->run calls the method, because we're
84                // warming the cache. We want to instrument cache misses when we get here from the ::get method,
85                // because that's called in BeforePageDisplay where we expect to have a cached result.
86                if ( $fname === __CLASS__ . '::get' ) {
87                    $statsdDataFactory->increment( 'GrowthExperiments.CacheBackedImageRecommendationProvider.miss' );
88                }
89
90                $response = $imageRecommendationProvider->get( $title, $taskType );
91                if ( $response instanceof StatusValue ) {
92                    $ttl = $cache::TTL_UNCACHEABLE;
93                }
94                return $response;
95            }
96        );
97    }
98
99    /**
100     * @param WANObjectCache $cache
101     * @param string $taskTypeId
102     * @param string $dbKey
103     * @return string
104     */
105    public static function makeKey( WANObjectCache $cache, string $taskTypeId, string $dbKey ): string {
106        return $cache->makeKey( 'GrowthExperiments', 'Recommendations', $taskTypeId, $dbKey );
107    }
108}