MediaWiki master
SpecBasedModule.php
Go to the documentation of this file.
1<?php
2
4
15use Wikimedia\ObjectFactory\ObjectFactory;
16
42
43 private string $definitionFile;
44
45 private ?array $moduleDef = null;
46
47 private ?int $routeFileTimestamp = null;
48
49 private ?string $configHash = null;
50
54 public function __construct(
55 string $definitionFile,
56 Router $router,
57 string $pathPrefix,
59 BasicAuthorizerInterface $basicAuth,
60 ObjectFactory $objectFactory,
61 Validator $restValidator,
62 ErrorReporter $errorReporter,
63 HookContainer $hookContainer
64 ) {
65 parent::__construct(
66 $router,
69 $basicAuth,
70 $objectFactory,
71 $restValidator,
72 $errorReporter,
73 $hookContainer
74 );
75 $this->definitionFile = $definitionFile;
76 }
77
81 protected function getConfigHash(): string {
82 if ( $this->configHash === null ) {
83 $this->configHash = md5( json_encode( [
84 'class' => __CLASS__,
85 'version' => 1,
86 'fileTimestamps' => $this->getRouteFileTimestamp()
87 ] ) );
88 }
89 return $this->configHash;
90 }
91
95 private function getModuleDefinition(): array {
96 if ( $this->moduleDef !== null ) {
97 return $this->moduleDef;
98 }
99
100 $this->routeFileTimestamp = filemtime( $this->definitionFile );
101 $this->moduleDef = static::loadModuleDefinition( $this->definitionFile, $this->responseFactory );
102
103 return $this->moduleDef;
104 }
105
114 public static function loadModuleDefinition( string $file, ResponseFactory $responseFactory ): array {
115 $moduleDef = static::loadJsonFile( $file );
116
117 // This does not guarantee the file is a valid flat route file, just that it appears
118 // to be of that format. Files containing only an empty array are assumed to be valid
119 // flat route files containing no routes rather than malformed module definition files.
120 // Some development flat route files in extensions, such as Wikibase's routes.dev.json,
121 // may exist as placeholders but intentionally contain no routes.
122 if ( array_is_list( $moduleDef ) ) {
123 throw new ModuleFormatException(
124 $file . ' appears to be a flat route file, not a module definition file.'
125 );
126 }
127
128 // The array_is_list() call above confuses phan. Force phan to consider $mwapi to be of
129 // the type we expect. Also check its type ourselves in code at runtime, just to be sure.
130 $mwapi = $moduleDef['mwapi'] ?? null;
131 '@phan-var ?string $mwapi';
132
133 if ( !is_string( $mwapi ) ) {
135 'Missing or malformed mwapi version field in ' . $file
136 );
137 }
138
139 // Require a supported version of mwapi
140 if ( version_compare( $mwapi, '1.0.0', '<' ) ||
141 version_compare( $mwapi, '1.1.999', '>' )
142 ) {
144 "Unsupported mwapi version {$mwapi} in "
145 . $file
146 );
147 }
148
149 $localizer = new JsonLocalizer( $responseFactory );
150 return $localizer->localizeJson( $moduleDef );
151 }
152
156 private function getRouteFileTimestamp(): int {
157 if ( $this->routeFileTimestamp === null ) {
158 $this->routeFileTimestamp = filemtime( $this->definitionFile );
159 }
160 return $this->routeFileTimestamp;
161 }
162
168 public function getDefinedPaths(): array {
169 $paths = [];
170 $moduleDef = $this->getModuleDefinition();
171
172 foreach ( $moduleDef['paths'] as $path => $pSpec ) {
173 $paths[$path] = [];
174 foreach ( $pSpec as $method => $opSpec ) {
175 $paths[$path][] = strtoupper( $method );
176 }
177 }
178
179 return $paths;
180 }
181
182 protected function initRoutes(): void {
183 $moduleDef = $this->getModuleDefinition();
184
185 // The structure is similar to OpenAPI, see docs/rest/mwapi.1.0.json
186 foreach ( $moduleDef['paths'] as $path => $pathSpec ) {
187 foreach ( $pathSpec as $method => $opSpec ) {
188 $info = $this->makeRouteInfo( $path, $opSpec );
189 $this->addRoute( $method, $path, $info );
190 }
191 }
192 }
193
199 private function makeRouteInfo( string $path, array $opSpec ): array {
200 static $objectSpecKeys = [
201 'class',
202 'factory',
203 'services',
204 'optional_services',
205 'args',
206 ];
207
208 static $oasKeys = [
209 'parameters',
210 'responses',
211 'summary',
212 'description',
213 'tags',
214 'externalDocs',
215 'deprecationSettings',
216 'operationId',
217 ];
218
219 if ( isset( $opSpec['redirect'] ) ) {
220 // Redirect shorthand
221 $opSpec['handler'] = [
222 'class' => RedirectHandler::class,
223 'redirect' => $opSpec['redirect'],
224 ];
225 unset( $opSpec['redirect'] );
226 }
227
228 $handlerSpec = $opSpec['handler'] ?? null;
229 if ( !$handlerSpec ) {
230 throw new RouteDefinitionException( 'Missing handler spec' );
231 }
232
233 $info = [
234 'spec' => array_intersect_key( $handlerSpec, array_flip( $objectSpecKeys ) ),
235 'config' => array_diff_key( $handlerSpec, array_flip( $objectSpecKeys ) ),
236 'openApiSpec' => array_intersect_key( $opSpec, array_flip( $oasKeys ) ),
237 'path' => $path,
238 ];
239
240 return $info;
241 }
242
244 public function getOpenApiInfo() {
245 $def = $this->getModuleDefinition();
246 return $def['info'] ?? [];
247 }
248
250 public function getOpenApiExternalDocs(): array {
251 $def = $this->getModuleDefinition();
252 return $def['externalDocs'] ?? [];
253 }
254
255}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
Utility class for json localization needs in the REST API.
MatcherBasedModules respond to requests by matching the requested path against a list of known routes...
Exception indicating incorrect REST module format.
ResponseFactory $responseFactory
Definition Module.php:45
A Module that is based on a module definition file similar to an OpenAPI spec.
initRoutes()
Initialize matchers by calling addRoute() for each known route.
static loadModuleDefinition(string $file, ResponseFactory $responseFactory)
Load the module definition file.
__construct(string $definitionFile, Router $router, string $pathPrefix, ResponseFactory $responseFactory, BasicAuthorizerInterface $basicAuth, ObjectFactory $objectFactory, Validator $restValidator, ErrorReporter $errorReporter, HookContainer $hookContainer)
getOpenApiInfo()
Return an array with data to be included in an OpenAPI "info" object describing this module....
getOpenApiExternalDocs()
Return an array with data to be included as the root-level OpenAPI "externalDocs" object describing t...
getConfigHash()
Get a config version hash for cache invalidation.
Exception indicating incorrect REST module configuration.
Generates standardized response objects.
The REST router is responsible for gathering module configuration, matching an input path against the...
Definition Router.php:31
Wrapper for ParamValidator.
Definition Validator.php:38
An interface used by Router to ensure that the client has "basic" access, i.e.
An ErrorReporter internally reports an error that happened during the handling of a request.