4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
25 namespace MediaWiki\Content
;
27 use InvalidArgumentException
;
28 use MediaWiki\HookContainer\HookContainer
;
29 use MediaWiki\HookContainer\HookRunner
;
30 use MWUnknownContentModelException
;
31 use Psr\Log\LoggerInterface
;
32 use Wikimedia\ObjectFactory\ObjectFactory
;
35 * Class ContentHandlerFactory
36 * @package MediaWiki\Content
40 final class ContentHandlerFactory
implements IContentHandlerFactory
{
43 * @var string[]|callable[]
45 private $handlerSpecs;
48 * @var ContentHandler[] Registry of ContentHandler instances by model id
50 private $handlersByModel = [];
52 /** @var ObjectFactory */
53 private $objectFactory;
55 /** @var HookRunner */
58 /** @var LoggerInterface */
63 * @internal Use @see MediaWikiServices::getContentHandlerFactory
65 * @param string[]|callable[] $handlerSpecs An associative array mapping each known
66 * content model to the ObjectFactory spec used to construct its ContentHandler.
67 * This array typically comes from $wgContentHandlers.
68 * @param ObjectFactory $objectFactory
69 * @param HookContainer $hookContainer
70 * @param LoggerInterface $logger
72 public function __construct(
74 ObjectFactory
$objectFactory,
75 HookContainer
$hookContainer,
76 LoggerInterface
$logger
78 $this->handlerSpecs
= $handlerSpecs;
79 $this->objectFactory
= $objectFactory;
80 $this->hookRunner
= new HookRunner( $hookContainer );
81 $this->logger
= $logger;
85 * @param string $modelID
87 * @return ContentHandler
88 * @throws MWUnknownContentModelException If no handler is known for the model ID.
90 public function getContentHandler( string $modelID ): ContentHandler
{
91 if ( empty( $this->handlersByModel
[$modelID] ) ) {
92 $contentHandler = $this->createForModelID( $modelID );
95 "Registered handler for {$modelID}: " . get_class( $contentHandler )
97 $this->handlersByModel
[$modelID] = $contentHandler;
100 return $this->handlersByModel
[$modelID];
104 * Define HandlerSpec for ModelID.
105 * @param string $modelID
106 * @param callable|string $handlerSpec
111 public function defineContentHandler( string $modelID, $handlerSpec ): void
{
112 if ( !is_callable( $handlerSpec ) && !is_string( $handlerSpec ) ) {
113 throw new InvalidArgumentException(
114 "ContentHandler Spec for modelID '{$modelID}' must be callable or class name"
117 unset( $this->handlersByModel
[$modelID] );
118 $this->handlerSpecs
[$modelID] = $handlerSpec;
122 * Get defined ModelIDs
126 public function getContentModels(): array {
127 $modelsFromHook = [];
128 $this->hookRunner
->onGetContentModels( $modelsFromHook );
129 $models = array_merge( // auto-registered from config and MediaWikiServices or manual
130 array_keys( $this->handlerSpecs
),
132 // incorrect registered and called: without HOOK_NAME_GET_CONTENT_MODELS
133 array_keys( $this->handlersByModel
),
135 // correct registered: as HOOK_NAME_GET_CONTENT_MODELS
138 return array_unique( $models );
144 public function getAllContentFormats(): array {
146 foreach ( $this->handlerSpecs
as $model => $class ) {
147 $formats +
= array_fill_keys(
148 $this->getContentHandler( $model )->getSupportedFormats(),
152 return array_keys( $formats );
156 * @param string $modelID
160 public function isDefinedModel( string $modelID ): bool {
161 return in_array( $modelID, $this->getContentModels(), true );
165 * Create ContentHandler for ModelID
167 * @param string $modelID The ID of the content model for which to get a handler.
168 * Use CONTENT_MODEL_XXX constants.
170 * @return ContentHandler The ContentHandler singleton for handling the model given by the ID.
172 * @throws MWUnknownContentModelException If no handler is known for the model ID.
174 private function createForModelID( string $modelID ): ContentHandler
{
175 $handlerSpec = $this->handlerSpecs
[$modelID] ??
null;
176 if ( $handlerSpec !== null ) {
177 return $this->createContentHandlerFromHandlerSpec( $modelID, $handlerSpec );
180 return $this->createContentHandlerFromHook( $modelID );
184 * @param string $modelID
185 * @param ContentHandler $contentHandler
187 * @throws MWUnknownContentModelException
189 private function validateContentHandler( string $modelID, $contentHandler ): void
{
190 if ( $contentHandler === null ) {
191 throw new MWUnknownContentModelException( $modelID );
194 if ( !is_object( $contentHandler ) ) {
195 throw new InvalidArgumentException(
196 "ContentHandler for model {$modelID} wrong: non-object given."
200 if ( !$contentHandler instanceof ContentHandler
) {
201 throw new InvalidArgumentException(
202 "ContentHandler for model {$modelID} must supply a ContentHandler instance, "
203 . get_class( $contentHandler ) . ' given.'
209 * @param string $modelID
210 * @param callable|string $handlerSpec
212 * @return ContentHandler
213 * @throws MWUnknownContentModelException
215 private function createContentHandlerFromHandlerSpec(
216 string $modelID, $handlerSpec
219 * @var ContentHandler $contentHandler
221 $contentHandler = $this->objectFactory
->createObject(
224 'assertClass' => ContentHandler
::class,
225 'allowCallable' => true,
226 'allowClassName' => true,
227 'extraArgs' => [ $modelID ],
231 $this->validateContentHandler( $modelID, $contentHandler );
233 return $contentHandler;
237 * @param string $modelID
239 * @return ContentHandler
240 * @throws MWUnknownContentModelException
242 private function createContentHandlerFromHook( string $modelID ): ContentHandler
{
243 $contentHandler = null;
244 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
245 $this->hookRunner
->onContentHandlerForModelID( $modelID, $contentHandler );
246 $this->validateContentHandler( $modelID, $contentHandler );
248 '@phan-var ContentHandler $contentHandler';
250 return $contentHandler;