MediaWiki master
SpecBasedModule.php
Go to the documentation of this file.
1<?php
2
4
14use Wikimedia\ObjectFactory\ObjectFactory;
15
41
42 private ?array $moduleDef = null;
43
44 private ?int $routeFileTimestamp = null;
45
46 private ?string $configHash = null;
47
51 public function __construct(
52 private readonly string $definitionFile,
53 Router $router,
54 string $pathPrefix,
55 JsonLocalizer $jsonLocalizer,
56 BasicAuthorizerInterface $basicAuth,
57 ObjectFactory $objectFactory,
58 Validator $restValidator,
59 ErrorReporter $errorReporter,
60 HookContainer $hookContainer,
61 ) {
62 parent::__construct(
63 $router,
64 $pathPrefix,
65 $jsonLocalizer,
66 $basicAuth,
67 $objectFactory,
68 $restValidator,
69 $errorReporter,
70 $hookContainer
71 );
72 }
73
77 protected function getConfigHash(): string {
78 if ( $this->configHash === null ) {
79 $this->configHash = md5( json_encode( [
80 'class' => __CLASS__,
81 'version' => 1,
82 'fileTimestamps' => $this->getRouteFileTimestamp()
83 ] ) );
84 }
85 return $this->configHash;
86 }
87
91 private function getModuleDefinition(): array {
92 if ( $this->moduleDef !== null ) {
93 return $this->moduleDef;
94 }
95
96 $this->routeFileTimestamp = filemtime( $this->definitionFile );
97 $this->moduleDef = static::loadModuleDefinition(
98 $this->definitionFile,
99 $this->getJsonLocalizer()
100 );
101
102 return $this->moduleDef;
103 }
104
113 public static function loadModuleDefinition( string $file, JsonLocalizer $localizer ): array {
114 $moduleDef = static::loadJsonFile( $file );
115
116 // This does not guarantee the file is a valid flat route file, just that it appears
117 // to be of that format. Files containing only an empty array are assumed to be valid
118 // flat route files containing no routes rather than malformed module definition files.
119 // Some development flat route files in extensions, such as Wikibase's routes.dev.json,
120 // may exist as placeholders but intentionally contain no routes.
121 if ( array_is_list( $moduleDef ) ) {
122 throw new ModuleFormatException(
123 $file . ' appears to be a flat route file, not a module definition file.'
124 );
125 }
126
127 // The array_is_list() call above confuses phan. Force phan to consider $mwapi to be of
128 // the type we expect. Also check its type ourselves in code at runtime, just to be sure.
129 $mwapi = $moduleDef['mwapi'] ?? null;
130 '@phan-var ?string $mwapi';
131
132 if ( !is_string( $mwapi ) ) {
134 'Missing or malformed mwapi version field in ' . $file
135 );
136 }
137
138 // Require a supported version of mwapi
139 if ( version_compare( $mwapi, '1.0.0', '<' ) ||
140 version_compare( $mwapi, '1.2.999', '>' )
141 ) {
143 "Unsupported mwapi version {$mwapi} in "
144 . $file
145 );
146 }
147
148 return $localizer->localizeJson( $moduleDef );
149 }
150
154 private function getRouteFileTimestamp(): int {
155 if ( $this->routeFileTimestamp === null ) {
156 $this->routeFileTimestamp = filemtime( $this->definitionFile );
157 }
158 return $this->routeFileTimestamp;
159 }
160
166 public function getDefinedPaths(): array {
167 $paths = [];
168 $moduleDef = $this->getModuleDefinition();
169
170 foreach ( $moduleDef['paths'] as $path => $pSpec ) {
171 $paths[$path] = [];
172 foreach ( $pSpec as $method => $opSpec ) {
173 $paths[$path][] = strtoupper( $method );
174 }
175 }
176
177 return $paths;
178 }
179
180 protected function initRoutes(): void {
181 $moduleDef = $this->getModuleDefinition();
182
183 // The structure is similar to OpenAPI, see docs/rest/mwapi.1.0.json
184 foreach ( $moduleDef['paths'] as $path => $pathSpec ) {
185 foreach ( $pathSpec as $method => $opSpec ) {
186 $info = $this->makeRouteInfo( $path, $opSpec );
187 $this->addRoute( $method, $path, $info );
188 }
189 }
190 }
191
197 private function makeRouteInfo( string $path, array $opSpec ): array {
198 static $objectSpecKeys = [
199 'class',
200 'factory',
201 'services',
202 'optional_services',
203 'args',
204 'adapter', // for known adapter classes
205 ];
206
207 static $oasKeys = [
208 'parameters',
209 'responses',
210 'summary',
211 'description',
212 'tags',
213 'externalDocs',
214 'deprecationSettings',
215 'operationId',
216 ];
217
218 if ( isset( $opSpec['redirect'] ) ) {
219 // Redirect shorthand
220 $opSpec['handler'] = [
221 'class' => RedirectHandler::class,
222 'redirect' => $opSpec['redirect'],
223 ];
224 unset( $opSpec['redirect'] );
225 }
226
227 $handlerSpec = $opSpec['handler'] ?? null;
228 if ( !$handlerSpec ) {
229 throw new RouteDefinitionException( 'Missing handler spec' );
230 }
231
232 $info = [
233 'spec' => array_intersect_key( $handlerSpec, array_flip( $objectSpecKeys ) ),
234 'config' => array_diff_key( $handlerSpec, array_flip( $objectSpecKeys ) ),
235 'openApiSpec' => array_intersect_key( $opSpec, array_flip( $oasKeys ) ),
236 'path' => $path,
237 ];
238
239 return $info;
240 }
241
243 public function getOpenApiInfo() {
244 $def = $this->getModuleDefinition();
245 return $def['info'] ?? [];
246 }
247
249 public function getOpenApiExternalDocs(): array {
250 $def = $this->getModuleDefinition();
251 return $def['externalDocs'] ?? [];
252 }
253
255 public function getOpenApiTags(): array {
256 $def = $this->getModuleDefinition();
257 return $def['tags'] ?? [];
258 }
259
260}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
A generic redirect handler for the REST API.
Utility class for json localization needs in the REST API.
localizeJson(array $json)
Recursively localizes name/value pairs, where the name begins with "x-i18-n-".
MatcherBasedModules respond to requests by matching the requested path against a list of known routes...
Exception indicating incorrect REST module format.
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.
getOpenApiInfo()
Return an array with data to be included in an OpenAPI "info" object describing this module....
__construct(private readonly string $definitionFile, Router $router, string $pathPrefix, JsonLocalizer $jsonLocalizer, BasicAuthorizerInterface $basicAuth, ObjectFactory $objectFactory, Validator $restValidator, ErrorReporter $errorReporter, HookContainer $hookContainer,)
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.
static loadModuleDefinition(string $file, JsonLocalizer $localizer)
Load the module definition file.
getOpenApiTags()
Return an array with data to be included in an OpenAPI "tags" object describing this module's tags....
Exception indicating incorrect REST module configuration.
The REST router is responsible for gathering module configuration, matching an input path against the...
Definition Router.php:34
Wrapper for ParamValidator.
Definition Validator.php:39
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.