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 if ( !$moduleDef ) {
119 'Malformed module definition file: ' . $file
120 );
121 }
122
123 if ( !isset( $moduleDef['mwapi'] ) ) {
125 'Missing mwapi version field in ' . $file
126 );
127 }
128
129 // Require a supported version of mwapi
130 if ( version_compare( $moduleDef['mwapi'], '1.0.0', '<' ) ||
131 version_compare( $moduleDef['mwapi'], '1.1.999', '>' )
132 ) {
134 "Unsupported mwapi version {$moduleDef['mwapi']} in "
135 . $file
136 );
137 }
138
139 $localizer = new JsonLocalizer( $responseFactory );
140 return $localizer->localizeJson( $moduleDef );
141 }
142
146 private function getRouteFileTimestamp(): int {
147 if ( $this->routeFileTimestamp === null ) {
148 $this->routeFileTimestamp = filemtime( $this->definitionFile );
149 }
150 return $this->routeFileTimestamp;
151 }
152
158 public function getDefinedPaths(): array {
159 $paths = [];
160 $moduleDef = $this->getModuleDefinition();
161
162 foreach ( $moduleDef['paths'] as $path => $pSpec ) {
163 $paths[$path] = [];
164 foreach ( $pSpec as $method => $opSpec ) {
165 $paths[$path][] = strtoupper( $method );
166 }
167 }
168
169 return $paths;
170 }
171
172 protected function initRoutes(): void {
173 $moduleDef = $this->getModuleDefinition();
174
175 // The structure is similar to OpenAPI, see docs/rest/mwapi.1.0.json
176 foreach ( $moduleDef['paths'] as $path => $pathSpec ) {
177 foreach ( $pathSpec as $method => $opSpec ) {
178 $info = $this->makeRouteInfo( $path, $opSpec );
179 $this->addRoute( $method, $path, $info );
180 }
181 }
182 }
183
189 private function makeRouteInfo( string $path, array $opSpec ): array {
190 static $objectSpecKeys = [
191 'class',
192 'factory',
193 'services',
194 'optional_services',
195 'args',
196 ];
197
198 static $oasKeys = [
199 'parameters',
200 'responses',
201 'summary',
202 'description',
203 'tags',
204 'externalDocs',
205 'deprecationSettings'
206 ];
207
208 if ( isset( $opSpec['redirect'] ) ) {
209 // Redirect shorthand
210 $opSpec['handler'] = [
211 'class' => RedirectHandler::class,
212 'redirect' => $opSpec['redirect'],
213 ];
214 unset( $opSpec['redirect'] );
215 }
216
217 $handlerSpec = $opSpec['handler'] ?? null;
218 if ( !$handlerSpec ) {
219 throw new RouteDefinitionException( 'Missing handler spec' );
220 }
221
222 $info = [
223 'spec' => array_intersect_key( $handlerSpec, array_flip( $objectSpecKeys ) ),
224 'config' => array_diff_key( $handlerSpec, array_flip( $objectSpecKeys ) ),
225 'openApiSpec' => array_intersect_key( $opSpec, array_flip( $oasKeys ) ),
226 'path' => $path,
227 ];
228
229 return $info;
230 }
231
233 public function getOpenApiInfo() {
234 $def = $this->getModuleDefinition();
235 return $def['info'] ?? [];
236 }
237
238}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
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...
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....
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:30
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.