Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
81.40% |
35 / 43 |
|
44.44% |
4 / 9 |
CRAP | |
0.00% |
0 / 1 |
ExternalStoreFactory | |
81.40% |
35 / 43 |
|
44.44% |
4 / 9 |
29.02 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
setLogger | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProtocols | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getWriteBaseUrls | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getStore | |
78.95% |
15 / 19 |
|
0.00% |
0 / 1 |
10.93 | |||
getStoreForUrl | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
getStoreLocationFromUrl | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
getUrlsByProtocol | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
splitStorageUrl | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
4.25 |
1 | <?php |
2 | |
3 | use MediaWiki\MediaWikiServices; |
4 | use Psr\Log\LoggerAwareInterface; |
5 | use Psr\Log\LoggerInterface; |
6 | use Psr\Log\NullLogger; |
7 | |
8 | /** |
9 | * @see ExternalStoreAccess |
10 | * @internal Use the ExternalStoreAccess service instead. |
11 | * @since 1.31 |
12 | * @ingroup ExternalStorage |
13 | */ |
14 | class ExternalStoreFactory implements LoggerAwareInterface { |
15 | /** @var string[] List of storage access protocols */ |
16 | private $protocols; |
17 | /** @var string[] List of base storage URLs that define locations for writes */ |
18 | private $writeBaseUrls; |
19 | /** @var string Default database domain to store content under */ |
20 | private $localDomainId; |
21 | /** @var LoggerInterface */ |
22 | private $logger; |
23 | |
24 | /** |
25 | * @param string[] $externalStores See $wgExternalStores |
26 | * @param string[] $defaultStores See $wgDefaultExternalStore |
27 | * @param string $localDomainId Local database/wiki ID |
28 | * @param LoggerInterface|null $logger |
29 | */ |
30 | public function __construct( |
31 | array $externalStores, |
32 | array $defaultStores, |
33 | string $localDomainId, |
34 | LoggerInterface $logger = null |
35 | ) { |
36 | $this->protocols = array_map( 'strtolower', $externalStores ); |
37 | $this->writeBaseUrls = $defaultStores; |
38 | $this->localDomainId = $localDomainId; |
39 | $this->logger = $logger ?: new NullLogger(); |
40 | } |
41 | |
42 | public function setLogger( LoggerInterface $logger ) { |
43 | $this->logger = $logger; |
44 | } |
45 | |
46 | /** |
47 | * @return string[] List of active store types/protocols (lowercased), e.g. [ "db" ] |
48 | * @since 1.34 |
49 | */ |
50 | public function getProtocols() { |
51 | return $this->protocols; |
52 | } |
53 | |
54 | /** |
55 | * @return string[] List of default base URLs for writes, e.g. [ "DB://cluster1" ] |
56 | * @since 1.34 |
57 | */ |
58 | public function getWriteBaseUrls() { |
59 | return $this->writeBaseUrls; |
60 | } |
61 | |
62 | /** |
63 | * Get an external store object of the given type, with the given parameters |
64 | * |
65 | * The 'domain' field in $params will be set to the local DB domain if it is unset |
66 | * or false. A special 'isDomainImplicit' flag is set when this happens, which should |
67 | * only be used to handle legacy DB domain configuration concerns (e.g. T200471). |
68 | * |
69 | * @param string $proto Type of external storage, should be a value in $wgExternalStores |
70 | * @param array $params Map of ExternalStoreMedium::__construct context parameters. |
71 | * @return ExternalStoreMedium The store class or false on error |
72 | * @throws ExternalStoreException When $proto is not recognized |
73 | */ |
74 | public function getStore( $proto, array $params = [] ) { |
75 | $protoLowercase = strtolower( $proto ); // normalize |
76 | if ( !$this->protocols || !in_array( $protoLowercase, $this->protocols ) ) { |
77 | throw new ExternalStoreException( "Protocol '$proto' is not enabled." ); |
78 | } |
79 | |
80 | if ( $protoLowercase === 'db' ) { |
81 | $class = 'ExternalStoreDB'; |
82 | } else { |
83 | $class = 'ExternalStore' . ucfirst( $proto ); |
84 | } |
85 | if ( isset( $params['wiki'] ) ) { |
86 | $params += [ 'domain' => $params['wiki'] ]; // b/c |
87 | } |
88 | if ( !isset( $params['domain'] ) || $params['domain'] === false ) { |
89 | $params['domain'] = $this->localDomainId; // default |
90 | $params['isDomainImplicit'] = true; // b/c for ExternalStoreDB |
91 | } |
92 | // @TODO: ideally, this class should not hardcode what classes need what backend factory |
93 | // objects. For now, inject the factory instances into __construct() for those that do. |
94 | if ( $protoLowercase === 'db' ) { |
95 | $params['lbFactory'] = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); |
96 | } elseif ( $protoLowercase === 'mwstore' ) { |
97 | $params['fbGroup'] = MediaWikiServices::getInstance()->getFileBackendGroup(); |
98 | } |
99 | $params['logger'] = $this->logger; |
100 | |
101 | if ( !class_exists( $class ) ) { |
102 | throw new ExternalStoreException( "Class '$class' is not defined." ); |
103 | } |
104 | |
105 | // Any custom modules should be added to $wgAutoLoadClasses for on-demand loading |
106 | return new $class( $params ); |
107 | } |
108 | |
109 | /** |
110 | * Get the ExternalStoreMedium for a given URL |
111 | * |
112 | * $url is either of the form: |
113 | * - a) "<proto>://<location>/<path>", for retrieval, or |
114 | * - b) "<proto>://<location>", for storage |
115 | * |
116 | * @param string $url |
117 | * @param array $params Map of ExternalStoreMedium::__construct context parameters |
118 | * @return ExternalStoreMedium |
119 | * @throws ExternalStoreException When the protocol is missing or not recognized |
120 | * @since 1.34 |
121 | */ |
122 | public function getStoreForUrl( $url, array $params = [] ) { |
123 | [ $proto, $path ] = self::splitStorageUrl( $url ); |
124 | if ( $path == '' ) { // bad URL |
125 | throw new ExternalStoreException( "Invalid URL '$url'" ); |
126 | } |
127 | |
128 | return $this->getStore( $proto, $params ); |
129 | } |
130 | |
131 | /** |
132 | * Get the location within the appropriate store for a given a URL |
133 | * |
134 | * @param string $url |
135 | * @return string |
136 | * @throws ExternalStoreException |
137 | * @since 1.34 |
138 | */ |
139 | public function getStoreLocationFromUrl( $url ) { |
140 | [ , $location ] = self::splitStorageUrl( $url ); |
141 | if ( $location == '' ) { // bad URL |
142 | throw new ExternalStoreException( "Invalid URL '$url'" ); |
143 | } |
144 | |
145 | return $location; |
146 | } |
147 | |
148 | /** |
149 | * @param string[] $urls |
150 | * @return string[][] Map of (protocol => list of URLs) |
151 | * @throws ExternalStoreException |
152 | * @since 1.34 |
153 | */ |
154 | public function getUrlsByProtocol( array $urls ) { |
155 | $urlsByProtocol = []; |
156 | foreach ( $urls as $url ) { |
157 | [ $proto, ] = self::splitStorageUrl( $url ); |
158 | $urlsByProtocol[$proto][] = $url; |
159 | } |
160 | |
161 | return $urlsByProtocol; |
162 | } |
163 | |
164 | /** |
165 | * @param string $storeUrl |
166 | * @return string[] (protocol, store location or location-qualified path) |
167 | * @throws ExternalStoreException |
168 | */ |
169 | private static function splitStorageUrl( $storeUrl ) { |
170 | $parts = explode( '://', $storeUrl ); |
171 | if ( count( $parts ) != 2 || $parts[0] === '' || $parts[1] === '' ) { |
172 | throw new ExternalStoreException( "Invalid storage URL '$storeUrl'" ); |
173 | } |
174 | |
175 | return $parts; |
176 | } |
177 | } |