Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.38% covered (warning)
78.38%
29 / 37
60.00% covered (warning)
60.00%
6 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
TaskTypeHandlerRegistry
78.38% covered (warning)
78.38%
29 / 37
60.00% covered (warning)
60.00%
6 / 10
19.92
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 has
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getByTaskType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getKnownIds
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 register
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getChangeTags
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getUniqueChangeTags
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getTaskTypeHandlerIdByChangeTagName
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 createHandler
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
1<?php
2
3namespace GrowthExperiments\NewcomerTasks\TaskType;
4
5use InvalidArgumentException;
6use OutOfBoundsException;
7use Wikimedia\ObjectFactory\ObjectFactory;
8
9/**
10 * Keeps track of TaskTypeHandlers.
11 */
12class TaskTypeHandlerRegistry {
13
14    /** @var ObjectFactory */
15    private $objectFactory;
16
17    /** @var (array|callable)[] ObjectFactory specifications or callbacks, keyed by handler ID. */
18    private $handlerSpecifications;
19
20    /** @var TaskTypeHandler[] Task type handlers, keyed by ID. */
21    private $handlers = [];
22
23    /**
24     * @param ObjectFactory $objectFactory
25     * @param array $handlerSpecifications ObjectFactory specifications or callbacks,
26     *   keyed by handler ID.
27     */
28    public function __construct( ObjectFactory $objectFactory, array $handlerSpecifications = [] ) {
29        $this->objectFactory = $objectFactory;
30        $this->handlerSpecifications = $handlerSpecifications;
31    }
32
33    /**
34     * @param string $handlerId TaskTypeHandler ID
35     * @return bool
36     */
37    public function has( string $handlerId ): bool {
38        return array_key_exists( $handlerId, $this->handlers )
39            || array_key_exists( $handlerId, $this->handlerSpecifications );
40    }
41
42    /**
43     * @param string $handlerId TaskTypeHandler ID
44     * @return TaskTypeHandler
45     * @throws OutOfBoundsException when invalid $handlerId is provided
46     */
47    public function get( string $handlerId ): TaskTypeHandler {
48        return $this->handlers[$handlerId] ?? $this->createHandler( $handlerId );
49    }
50
51    /**
52     * @param TaskType $taskType
53     * @return TaskTypeHandler
54     */
55    public function getByTaskType( TaskType $taskType ): TaskTypeHandler {
56        return $this->get( $taskType->getHandlerId() );
57    }
58
59    /**
60     * Returns a list of all handle TaskTypeHandler IDs that would be accepted by get().
61     * @return array
62     */
63    public function getKnownIds(): array {
64        $knownIds = array_keys( $this->handlerSpecifications );
65        sort( $knownIds );
66        return $knownIds;
67    }
68
69    /**
70     * @param string $handlerId
71     * @param array|callable $spec
72     * @throws InvalidArgumentException When a handler is already registered for the given ID.
73     */
74    public function register( string $handlerId, $spec ): void {
75        if ( array_key_exists( $handlerId, $this->handlerSpecifications ) ) {
76            throw new InvalidArgumentException( 'A task type handler is already registered for the ID '
77                . $handlerId );
78        }
79        $this->handlerSpecifications[$handlerId] = $spec;
80    }
81
82    /**
83     * Gets all the edit tags defined by all the possible task types.
84     * @return string[]
85     */
86    public function getChangeTags(): array {
87        $changeTags = [];
88        foreach ( $this->getKnownIds() as $handlerId ) {
89            $handler = $this->get( $handlerId );
90            $changeTags = array_merge( $changeTags, $handler->getChangeTags() );
91        }
92        return array_unique( $changeTags );
93    }
94
95    /**
96     * Get all the change tag names for all possible task types, excluding the "newcomer task" tag which applies
97     * to all the task types.
98     *
99     * @return string[]
100     */
101    public function getUniqueChangeTags(): array {
102        return array_values( array_filter(
103            $this->getChangeTags(), fn ( $changeTagName ) => $changeTagName !== TaskTypeHandler::NEWCOMER_TASK_TAG
104        ) );
105    }
106
107    /**
108     * Return the task type handler ID associated with a change tag.
109     *
110     * @param string $changeTagName The change tag name, e.g. "newcomer task copyedit"
111     * @return string|null
112     *  - The handler ID, e.g. "template-based" for unstructured tasks, or "link-recommendation" or
113     *    "image-recommendation" for structured tasks.
114     *  - null if the change tag could apply to multiple task types (e.g. "newcomer task") or if the change tag
115     *    name is unknown.
116     */
117    public function getTaskTypeHandlerIdByChangeTagName( string $changeTagName ): ?string {
118        if ( $changeTagName === TaskTypeHandler::NEWCOMER_TASK_TAG ) {
119            // Special-case the generic "newcomer task" tag because it applies to all task type handlers.
120            return null;
121        }
122        foreach ( $this->getKnownIds() as $handlerId ) {
123            $handler = $this->get( $handlerId );
124            if ( in_array( $changeTagName, $handler->getChangeTags() ) ) {
125                return $handler->getId();
126            }
127        }
128        return null;
129    }
130
131    /**
132     * @param string $handlerId
133     * @return TaskTypeHandler
134     * @throws OutOfBoundsException When there is no handler registered for the given ID.
135     */
136    private function createHandler( string $handlerId ): TaskTypeHandler {
137        $spec = $this->handlerSpecifications[$handlerId] ?? null;
138        if ( !$spec ) {
139            throw new OutOfBoundsException( 'No task type handler registered for the ID ' . $handlerId );
140        }
141        $handler = $this->objectFactory->createObject( $spec, [
142            'assertClass' => TaskTypeHandler::class,
143            'allowCallable' => true,
144        ] );
145        /** @var TaskTypeHandler $handler */'@phan-var TaskTypeHandler $handler';
146        $this->handlers[$handlerId] = $handler;
147        return $handler;
148    }
149
150}