zoukankan      html  css  js  c++  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()等方法。

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

    众多的SpeicalPage 的子类,系统使用工厂模式管理SpecialPage 以及其他父类的众多子类的实例( Page ) , 关于怎么创建
    SpecialPageFactory 实例,这里你可以看源代码,我们重点来说下工厂模式,以SpecialListFiles 为例:

    SpecialPageFactory 代码:
    /Specialpage/SpecialPageFactory.php
    private 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图:

    小问题:此刻,创建的SpecialListFiles 对象由谁持有?此处不表。
    我们再来看SpecialListFiles 与SpecialPage。
    上下文mContext 实例对象是如何创建的?在父类SpecialPage 中mContext 是一个接口的Reference(Java/C++这么说),请看图:

    在创建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
    " );
    return RequestContext::getMain();
    }
    }
    

      请看图:

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

    /includes/context/RequestContext.php
    /** Static methods **/
    /**
    * Get the RequestContext object associated with the main request
    *
    * @return RequestContext
    */
    public static function getMain() {
    static $instance = null;
    if ( $instance === null ) {
    $instance = new self;
    }
    return $instance;
    }

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

    /includes/context/RequestContext.php
    /**
    * Get the WebRequest object
    *
    * @return WebRequest
    */
    public function getRequest() {
    if ( $this->request === null ) {
    global $wgRequest; # fallback to $wg till we can improve this
    $this->request = $wgRequest;
    }
    return $this->request;
    }

    以SpecialListFile 为例,谈下显示图像清单的程序。
    在SpecialListFile 的图像显示界面,mediawiki 又交给了一个pager 的实现类ImageListPager 完成。SpecialListFile 创建一个ImageListPager 对象,完成相关的list 表格和图像的Thumb(拇指)图像的显示。
    请看创建ImageListPager 对象的UML 图示:

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

    return $this->msg( 'listfiles-latestversion-' .
    $value );
    default:
    throw new MWException( "Unknown field '$field'" );
    }
    }</code>
    Thumb pic 对象的操作(transform 及 toHtml)指令代码:
    <code>includes/specials/SpecialListfiles.php
    case '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 );

    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
    path
    if ( $this->repo ) {
    // Defer rendering if a 404 handler is set up...
    if ( $this->repo->canTransformVia404() && !( $flags &
    self::RENDER_NOW ) ) {
    wfDebug( __METHOD__ . " transformation deferred.
    " );
    // 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
    " );
    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
    " );
    }
    }
    // 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
    */
    function getRel() {
    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
    */
    function getHashPath() {
    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.php
    public 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 = new ImageListPager(
    $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<br />
    $body<br />
    $nav";
    }
    $this->getOutput()->addHTML( $html );
    }</code></pre>
    说明SpecialListFile 是由其父类SpecialPage 的终极方法run() 调用。
    在execute(),这一段代码是显示图片列表指令:<pre><code>/includes/specials/SpecialListfiles.php
    if ( $this->including() ) {
    $html = $pager->getBody();
    //这是显示部分。
    }

    关系如图:

    execute 中,另一个关键指令是:
    /includes/specials/SpecialListfiles.php
    $this->getOutput()->addHTML( $html );
    为什么会有一个getOutput 方法,而有关getOutput()方法,背后的架构是什么?透过OutputPage 开篇注释语:Preparation for the final page rendering.这让我想起来Android 的GUI 机制。也让我们明白了mediawiki 中的GUI,请看下UML 图:

    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.php
    private function main() {
    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图例:

    调用output()方法:

  • 相关阅读:
    线程内唯一对象HttpContext
    asp.net MVC 路由机制
    asp.net MVC 路由机制 Route
    asp.net MVC 路由系统
    asp.net中的路由系统
    Nodejs 高并发长链接TCP链接的服务器设计问题
    关于SharePoint 2010中不能使用AjaxControlToolkit的解决办法
    百度编辑器批量上传图片自动排版
    phpcms后台部分修改
    PHP导入excel发送网易云信短信
  • 原文地址:https://www.cnblogs.com/fancing/p/6377189.html
Copyright © 2011-2022 走看看