Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.00% covered (danger)
50.00%
13 / 26
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExperimentUserManager
50.00% covered (danger)
50.00%
13 / 26
20.00% covered (danger)
20.00%
1 / 5
13.12
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getVariant
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 setVariant
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 isUserInVariant
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isValidVariant
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments;
4
5use MediaWiki\Config\ServiceOptions;
6use MediaWiki\User\Options\UserOptionsLookup;
7use MediaWiki\User\Options\UserOptionsManager;
8use MediaWiki\User\UserFactory;
9use MediaWiki\User\UserIdentity;
10use Psr\Log\LoggerInterface;
11
12/**
13 * Service for handling experiment / variant related functions for users.
14 */
15class ExperimentUserManager {
16
17    private LoggerInterface $logger;
18    private ServiceOptions $options;
19    private UserOptionsLookup $userOptionsLookup;
20    private UserOptionsManager $userOptionsManager;
21    private UserFactory $userFactory;
22
23    public const CONSTRUCTOR_OPTIONS = [
24        'GEHomepageDefaultVariant',
25    ];
26
27    /**
28     * @param LoggerInterface $logger
29     * @param ServiceOptions $options
30     * @param UserOptionsManager $userOptionsManager
31     * @param UserOptionsLookup $userOptionsLookup
32     * @param UserFactory $userFactory
33     */
34    public function __construct(
35        LoggerInterface $logger,
36        ServiceOptions $options,
37        UserOptionsManager $userOptionsManager,
38        UserOptionsLookup $userOptionsLookup,
39        UserFactory $userFactory
40    ) {
41        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
42        $this->logger = $logger;
43        $this->options = $options;
44        $this->userOptionsLookup = $userOptionsLookup;
45        $this->userOptionsManager = $userOptionsManager;
46        $this->userFactory = $userFactory;
47    }
48
49    /**
50     * @param UserIdentity $user
51     * @return string
52     */
53    public function getVariant( UserIdentity $user ): string {
54        if ( !$this->userFactory->newFromUserIdentity( $user )->isNamed() ) {
55            $this->logger->debug( __METHOD__ . ' suspicious evaluation of unamed user', [
56                'exception' => new \RuntimeException,
57                'userName' => $user->getName(),
58                'trace' => \debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS )
59            ] );
60        }
61        $variant = $this->userOptionsLookup->getOption(
62            $user,
63            VariantHooks::USER_PREFERENCE
64        );
65        if ( !in_array( $variant, VariantHooks::VARIANTS ) ) {
66            $variant = $this->options->get( 'GEHomepageDefaultVariant' );
67        }
68        return $variant;
69    }
70
71    /**
72     * Set (but does not save) the variant for a user.
73     *
74     * @param UserIdentity $user
75     * @param string $variant
76     */
77    public function setVariant( UserIdentity $user, string $variant ): void {
78        $this->userOptionsManager->setOption(
79            $user,
80            VariantHooks::USER_PREFERENCE,
81            $variant
82        );
83    }
84
85    /**
86     * @param UserIdentity $user
87     * @param string|string[] $variant
88     * @return bool
89     */
90    public function isUserInVariant( UserIdentity $user, $variant ): bool {
91        return in_array( $this->getVariant( $user ), (array)$variant );
92    }
93
94    /**
95     * @param string $variant
96     * @return bool
97     */
98    public function isValidVariant( string $variant ): bool {
99        return in_array( $variant, VariantHooks::VARIANTS );
100    }
101}