mediawiki java_PHP 面向对象及Mediawiki 框架分析(一)

此文是一JAVA哥大神写的,虽然他不懂PHP。我这人PHP半桶水,面向对象更是半桶水都没有,此文原本是为了让我理解MediaWiki的运行机制的,愣是用他的JAVA的面向对象知识,对Mediawiki程序源码进行了一个整体剖析,膜拜!此文涉及诸多设计模式方面的知识,想搞MediaWiki的人,还是蛮有参考价值的。

本文除了原初要解决如何在第三方系统调用mediawiki 的图片文件资源外,也探寻了 Mediawiki 的GUI(Graphical User Interface)模式等。由于Mediawiki 的大部分页面是以SpecialPage 为引导,所以我以SpecialPages 为切入点进入分析Mediawiki 的架构。

SpecialPages 是所有 special pages 的父类。有关SpecialPage 父类的内在结构,有几个重要部分分解:

1、SpecialPage 父类中有mContext 上下文接口,供SpecialPage 的相关子类调用。接口含有getOutput()\getConfig()等方法。

6176387505a2c254055db6cf94a10871.png

这里首先来分析下SpecialPage 子类与分类的继承关系。以及子类的实例化。

继承关系:

15bc1a33852034bbdda392b390c730be.png

众多的SpeicalPage 的子类,系统使用工厂模式管理SpecialPage 以及其他父类的众多子类的实例( Page ) , 关于怎么创建

SpecialPageFactory 实例,这里你可以看源代码,我们重点来说下工厂模式,以SpecialListFiles 为例:

SpecialPageFactory 代码:/Specialpage/SpecialPageFactory.phpprivate static $list = array(//Media reports and uploads

'Listfiles' => 'SpecialListFiles',

'Filepath' => 'SpecialFilepath',

'MIMEsearch' => 'MIMEsearchPage',

'FileDuplicateSearch' => 'FileDuplicateSearchPage',

'Upload' => 'SpecialUpload',

'UploadStash' => 'SpecialUploadStash',

'ListDuplicatedFiles' => 'ListDuplicatedFilesPage',

再看获取'Listfiles' => 'SpecialListFiles'页面的getPage()方法:

/Specialpage/SpecialPageFactory.php/**

* Find the object with a given name and return it (or NULL)

*

* @param string $name Special page name, may be localised and/or

an alias

* @return SpecialPage|null SpecialPage object or null if the

page doesn't exist*/

public static function getPage( $name) {list( $realName, /*...*/ ) = self::resolveAlias( $name);if ( property_exists( self::getList(), $realName) ) {$rec = self::getList()->$realName;if ( is_string( $rec) ) {$className = $rec;return new $className;

}elseif ( is_array( $rec) ) {//@deprecated, officially since 1.18, unofficially

since forever

wfDebug("Array syntax for \$wgSpecialPages is

deprecated," .

"define a subclass of SpecialPage instead.");$className = array_shift( $rec);

self::getList()->$realName =MWFunction::newObj( $className, $rec);

}return self::getList()->$realName;

}else{return null;

}

}

关键指令:

self::getList()->$realName = MWFunction::newObj( $className, $rec );

请看UML图:

441dbf5ca9e9cdbecaee2f69019591b4.png

小问题:此刻,创建的SpecialListFiles 对象由谁持有?此处不表。

我们再来看SpecialListFiles 与SpecialPage。

上下文mContext 实例对象是如何创建的?在父类SpecialPage 中mContext 是一个接口的Reference(Java/C++这么说),请看图:

3297a06cb65803766798211f3f0402ef.png

在创建SpeicalFileLists 对象的过程中,父类完成了上下文接口的实现(RequestContext),创建过程:

代码:

/Specialpage/SpecialPage.php

/**

* Gets the context(这当中省略 that)this SpecialPage is executed

in

*

* @return IContextSource|RequestContext

* @since 1.18

*/

public function getContext() {

if ( $this->mContext instanceof IContextSource ) {

return $this->mContext;

} else {

wfDebug( __METHOD__ . " called and \$mContext is null. " .

"Return RequestContext::getMain(); for sanity\n" );

return RequestContext::getMain();

}

}

请看图:

b0e715518b38c7b010d5d7f251426b7e.png

在面向对象编程中,既然RequestContext 是类,不是对象,为什么还可以调用其getMain()方法?解释,getMain()方法是一个static静态方法。在getMain()方法中:

代码:

/includes/context/RequestContext.php/** Static methods **/

/**

* Get the RequestContext object associated with the main request

*

* @return RequestContext*/

public static functiongetMain() {static $instance = null;if ( $instance === null) {$instance = newself;

}return $instance;

}

有关SpeicalPage 的相关子类如何向服务器发送请求:这里的过程实际上是将RequestContext 对象的Reference 交给getContext()方法,实际上就是SpecialPage 的mContext。如此,透过上下文mContext Reference 调用getRequest():

getRequest()代码:

/includes/context/RequestContext.php/**

* Get the WebRequest object

*

* @return WebRequest*/

public functiongetRequest() {if ( $this->request === null) {global $wgRequest; #fallback to $wg till we can improve this

$this->request = $wgRequest;

}return $this->request;

}

1b1501c5753a9790fe0961cc572fd655.png

以SpecialListFile 为例,谈下显示图像清单的程序。

在SpecialListFile 的图像显示界面,mediawiki 又交给了一个pager 的实现类ImageListPager 完成。SpecialListFile 创建一个ImageListPager 对象,完成相关的list 表格和图像的Thumb(拇指)图像的显示。

请看创建ImageListPager 对象的UML 图示:

cc1e9030d43fd3cdb602bd9730eb6238.png

ImageListPager 对象处理图像数据方法是formatValue()。

formatValue()代码:

return $this->msg( 'listfiles-latestversion-' .

$value);default:

throw new MWException( "Unknown field '$field'");

}

}Thumb pic 对象的操作(transform 及 toHtml)指令代码:includes/specials/SpecialListfiles.phpcase 'thumb':

$opt = array( 'time' =>

$this->mCurrentRow->img_timestamp );$file =RepoGroup::singleton()->getLocalRepo()->findFile( $value, $opt);//If statement for paranoia

if ( $file) {$thumb = $file->transform( array( 'width' => 180,

'height' => 360) );return $thumb->toHtml( array( 'desc-link' =>

true) );

}else{return htmlspecialchars( $value);

}

获取文件(也可能是寻找到现有存在/…/…/的文件):

$file = RepoGroup::singleton()->getLocalRepo()->findFile( $value,$opt );

77c625e72d907f058cad3429819b6b25.png

findFile 方法返回true/false,findFile()代码:

includes/filerepo/RepoGroup.php/**

* Search repositories for an image.

* You can also use wfFindFile() to do this.

*

* @param $title Title|string Title object or string

* @param array $options Associative array of options:

* time: requested time for an archived image, or false for

the

* current version. An image object will be returned

which was

* created at the specified time.

* ignoreRedirect: If true, do not follow file redirects

* private: If true, return restricted (deleted) files if the

current

* user is allowed to view them. Otherwise, such files

will not

* be found.

* bypassCache: If true, do not use the process-local cache of

File objects

* @return File|bool False if title is not found*/

function findFile( $title, $options = array() ) {if ( !is_array( $options) ) {//MW 1.15 compat

$options = array( 'time' => $options);

}if ( !$this->reposInitialised ) {$this->initialiseRepos();

}$title = File::normalizeTitle( $title);if ( !$title) {return false;

}#Check the cache

if ( empty( $options['ignoreRedirect'] )&& empty( $options['private'] )&& empty( $options['bypassCache'] )

) {$time = isset( $options['time'] ) ? $options['time'] : '';$dbkey = $title->getDBkey();if ( $this->cache->has( $dbkey, $time, 60) ) {return $this->cache->get( $dbkey, $time);

}$useCache = true;

}else{$useCache = false;

}#Check the local repo

$image = $this->localRepo->findFile( $title, $options);#Check the foreign repos

if ( !$image) {foreach ( $this->foreignRepos as $repo) {$image = $repo->findFile( $title, $options);if ( $image) {break;

}

}

}$image = $image ? $image : false; //type sanity#Cache file existence or non-existence

if ( $useCache && ( !$image || $image->isCacheable() ) ) {$this->cache->set( $dbkey, $time, $image);

}return $image;

}

Image 对象thumb 输出:

File 类的图像处理方法 transform():

/includes/filerepo/file/File.php/**

* Transform a media file

*

* @param array $params an associative array of handler-specific

parameters.

* Typical keys are width, height and page.

* @param int $flags A bitfield, may contain self::RENDER_NOW to force

rendering

* @return MediaTransformOutput|bool False on failure*/

function transform( $params, $flags = 0) {global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;

wfProfileIn(__METHOD__);do{if ( !$this->canRender() ) {$thumb = $this->iconThumb();break; //not a bitmap or renderable image, don't try

}//Get the descriptionUrl to embed it as comment into the

thumbnail. Bug 19791.

$descriptionUrl = $this->getDescriptionUrl();if ( $descriptionUrl) {$params['descriptionUrl'] =wfExpandUrl($descriptionUrl,PROTO_CANONICAL );

}$handler = $this->getHandler();$script = $this->getTransformScript();if ( $script && !( $flags & self::RENDER_NOW ) ) {//Use a script to transform on client request, if possible

$thumb = $handler->getScriptedTransform( $this, $script,

$params);if ( $thumb) {break;

}

}$normalisedParams = $params;$handler->normaliseParams( $this, $normalisedParams);$thumbName = $this->thumbName( $normalisedParams);$thumbUrl = $this->getThumbUrl( $thumbName);$thumbPath = $this->getThumbPath( $thumbName ); //final thumb

pathif ( $this->repo ) {//Defer rendering if a 404 handler is set up...

if ( $this->repo->canTransformVia404() && !( $flags &self::RENDER_NOW ) ) {

wfDebug(__METHOD__ . " transformation deferred.\n");//XXX: Pass in the storage path even though we are not

rendering anything//and the path is supposed to be an FS path. This is

due to getScalerType()//getting called on the path and clobbering

$thumb->getUrl() if it's false.

$thumb = $handler->getTransform( $this, $thumbPath,

$thumbUrl, $params );

break;

}

// Clean up broken thumbnails as needed

$this->migrateThumbFile( $thumbName );

// Check if an up-to-date thumbnail already exists...

wfDebug( __METHOD__ . ": Doing stat for $thumbPath\n" );

if ( !( $flags & self::RENDER_FORCE ) &&

$this->repo->fileExists( $thumbPath ) ) {

$timestamp =

$this->repo->getFileTimestamp( $thumbPath );

if ( $timestamp !== false && $timestamp >=

$wgThumbnailEpoch ) {

// XXX: Pass in the storage path even though we are

not rendering anything

// and the path is supposed to be an FS path. This

is due to getScalerType()

// getting called on the path and clobbering

$thumb->getUrl() if it's false.

$thumb = $handler->getTransform( $this,

$thumbPath, $thumbUrl, $params);$thumb->setStoragePath( $thumbPath);break;

}

}elseif ( $flags & self::RENDER_FORCE ) {

wfDebug(__METHOD__ . "forcing rendering per flag

File::RENDER_FORCE\n");

}

}//If the backend is ready-only, don't keep generating

thumbnails//only to return transformation errors, just return the error

now.

if ( $this->repo->getReadOnlyReason() !== false) {$thumb = $this->transformErrorOutput( $thumbPath,

$thumbUrl, $params, $flags);break;

}//Create a temp FS file with the same extension and the

thumbnail$thumbExt = FileBackend::extensionFromPath( $thumbPath);$tmpFile = TempFSFile::factory( 'transform_', $thumbExt);if ( !$tmpFile) {$thumb = $this->transformErrorOutput( $thumbPath,

$thumbUrl, $params, $flags);break;

}$tmpThumbPath = $tmpFile->getPath(); //path of 0-byte temp

file

//Actually render the thumbnail...

wfProfileIn( __METHOD__ . '-doTransform');$thumb = $handler->doTransform( $this, $tmpThumbPath,

$thumbUrl, $params);

wfProfileOut(__METHOD__ . '-doTransform');$tmpFile->bind( $thumb ); //keep alive with $thumb

if ( !$thumb ) { //bad params?

$thumb = null;

}elseif ( $thumb->isError() ) { //transform error

$this->lastError = $thumb->toText();//Ignore errors if requested

if ( $wgIgnoreImageErrors && !( $flags &self::RENDER_NOW ) ) {$thumb = $handler->getTransform( $this,

$tmpThumbPath, $thumbUrl, $params);

}

}elseif ( $this->repo && $thumb->hasFile()&& !$thumb->fileIsSource() ) {//Copy the thumbnail from the file system into storage...

$disposition = $this->getThumbDisposition( $thumbName);$status = $this->repo->quickImport( $tmpThumbPath,

$thumbPath, $disposition);if ( $status->isOK() ) {$thumb->setStoragePath( $thumbPath);

}else{$thumb = $this->transformErrorOutput( $thumbPath,

$thumbUrl, $params, $flags);

}//Give extensions a chance to do something with this

thumbnail...wfRunHooks('FileTransformed', array( $this, $thumb,

$tmpThumbPath, $thumbPath) );

}//Purge. Useful in the event of Core -> Squid connection failure

or squid//purge collisions from elsewhere during failure. Don't keep

triggering for

//"thumbs" which have the main image URL though (bug 13776)

if ( $wgUseSquid) {if ( !$thumb || $thumb->isError() || $thumb->getUrl() !=

$this->getURL() ) {

SquidUpdate::purge( array( $thumbUrl) );

}

}

}while ( false);

wfProfileOut(__METHOD__);return is_object( $thumb ) ? $thumb : false;

}

代码中的红字部分即是真是的图片地址。

获取地址的getThumbPath()、getThumbRel()、getRel()方法:

/includes/filerepo/file/File.php/**

* Get the path of the thumbnail directory, or a particular file if

$suffix is specified

*

* @param bool|string $suffix If not false, the name of a thumbnail

file

* @return string*/

function getThumbPath( $suffix = false) {$this->assertRepoDefined();return $this->repo->getZonePath( 'thumb' ) . '/' .

$this->getThumbRel( $suffix);

}/**

* Get the path, relative to the thumbnail zone root, of the

* thumbnail directory or a particular file if $suffix is specified

*

* @param bool|string $suffix if not false, the name of a thumbnail

file

* @return string*/

function getThumbRel( $suffix = false) {$path = $this->getRel();if ( $suffix !== false) {$path .= '/' . $suffix;

}return $path;

}/**

* Get the path of the file relative to the public zone root.

* This function is overriden in OldLocalFile to be like

getArchiveRel().

*

* @return string*/

functiongetRel() {return $this->getHashPath() . $this->getName();

}/**

* Get the filename hash component of the directory including trailing

slash,

* e.g. f/fa/

* If the repository is not hashed, returns an empty string.

*

* @return string*/

functiongetHashPath() {if ( !isset( $this->hashPath ) ) {$this->assertRepoDefined();$this->hashPath =

$this->repo->getHashPath( $this->getName() );

}return $this->hashPath;

}

这样就有html 内容结果了:

return $thumb->toHtml( array( 'desc-link' => true ) );

至此,Thumb 拇指图片的数据检索,生成完毕。如何调用,也就明了。

有关单独的图片显示或者插入图片的显示过程,可待补充。

根据以下内容的框架属性,我把以下内容都归属于mediawiki 的GUI部分。SpecialListFile 的html 界面显示输出:

/includes/specials/SpecialListfiles.phppublic function execute( $par) {$this->setHeaders();$this->outputHeader();if ( $this->including() ) {$userName = $par;$search = '';$showAll = false;

}else{$userName = $this->getRequest()->getText( 'user', $par);$search = $this->getRequest()->getText( 'ilsearch', '');$showAll = $this->getRequest()->getBool( 'ilshowall',

false);

}$pager = newImageListPager($this->getContext(),

$userName,

$search,

$this->including(),

$showAll);if ( $this->including() ) {$html = $pager->getBody();//这是显示部分。

} else{$form = $pager->getForm();$body = $pager->getBody();$nav = $pager->getNavigationBar();$html = "$form
\n$body
\n$nav";

}$this->getOutput()->addHTML( $html);

}说明SpecialListFile 是由其父类SpecialPage 的终极方法run() 调用。

在execute(),这一段代码是显示图片列表指令:

/includes/specials/SpecialListfiles.phpif ( $this->including() ) {$html = $pager->getBody();//这是显示部分。

}

关系如图:

9cffaea42c2d32add4a49716d6f8d14c.png

execute 中,另一个关键指令是:

/includes/specials/SpecialListfiles.php

$this->getOutput()->addHTML( $html );

为什么会有一个getOutput 方法,而有关getOutput()方法,背后的架构是什么?透过OutputPage 开篇注释语:Preparation for the final page rendering.这让我想起来Android 的GUI 机制。也让我们明白了mediawiki 中的GUI,请看下UML 图:

eedfeeb0394e35f7c2701a0ba83f183a.png

addHTML()代码:

/includes/OutputPage.php/**

* Append $text to the body HTML

*

* @param string $text HTML*/

public function addHTML( $text) {$this->mBodytext .= $text;

}

MediaWiki的GUI架构的核心就是,透过OutputPage 的output()方法打印出结果:

Output()方法的代码:

/includes/OutputPage.php$response = $this->getRequest()->response();if ( $this->mArticleBodyOnly ) {echo $this->mBodytext;

}

看到这里,我们要问output方法由谁调用?哈哈哈,在查遍所有代码发现,output方法是由MediaWiki类(在wiki.php)中,在run()方法中执行mediawiki类的私有方法main()时,被执行。

includes/Wiki.phpprivate functionmain() {if(…)$this->context->setTitle( $title);$output = $this->context->getOutput();//Since we only do this redir to change proto, always

send a vary header

$output->addVaryHeader( 'X-Forwarded-Proto');$output->redirect( $redirUrl);$output->output();

wfProfileOut(__METHOD__);

…//Output everything!

$this->context->getOutput()->output();

wfProfileOut(__METHOD__);

}

而对于MediaWiki则是在访问index.php时,即被创建实例。有关输出的指令,请以下UML图例:

835b5b28d27888ca47a03d56ce98d803.png

调用output()方法:

2e27f073fbfc484c8508c13b31a7c79b.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值