Thomas Chin
Thomas Chin
Software Engineer III, Wikimedia Foundation
"Is that a bird? No, that's a moose." - me without my glasses.

About me

My hobby is learning new hobbies. Currently I'm juggling learning Japanese, 5-string bass, Drums, and CAD. My current/former online presence includes being a Twitch affiliate, YouTube partner, and Spotify artist.

My work

I work as a software engineer on the Platform Engineering team, formerly on the Expedition and API Platform sub-team. I currently work on and am and a founding member of the Event Platform sub-team. Read my user guide on how to talk to me.

Disclaimer: I work for or provide services to the Wikimedia Foundation, and this is the account I try to use for edits or statements I make in that role. However, the Foundation does not vet all my activity, so edits, statements, or other contributions made by this account may not reflect the views of the Foundation.

Contact me

User language
en-N This user has a native understanding of English.
ja-2 この利用者は中級日本語ができます。
yue-0 呢位用戶完全唔識(或者好難明)粵文
Users by language


Useful MediaWiki Dev Tidbits

Disclaimer: This is a brain dump. Most of this stuff I'm writing from memory and the information can be outdated.


Internationalization vs Localization

  • Internationalization: Designing software so it can be adapted to different languages without engineering changes
    • MediaWiki passes keys and params around that represent localizable strings
  • Localization: The process of adapting internationalized software for a language/region
    • MediaWiki then converts the key into the localized string using a metric crap ton of json files
  • We are currently trying to separate the logic that does internationalization from the logic that does localization
Message vs MessageValue
  • Message does both internationalization and localization (messy and bad, uses global state which is terrible for testing)
  • MessageValue is a new way to store message data independent of MediaWiki and without the messy formatting logic
  • Formatter logic now stored in a separate TextFormatter
  • Old Way:
wfMessage( 'message-key' )->params( ... )

  • New Way:
$textFormatter = MediaWikiServices::getMessageFormatterFactory()
  ->getTextFormatter( 'en' );
  MessageValue::new( 'message-key' )->textParams( ... )

Interface Explanations

  • PageReference: Potentially viewable or linkable object (could be an interwiki link)
  • PageIdentity: A page that may exist (could be a special page)
  • ProperPageIdentity: An editable page (not special page) that could exist (may not be created yet)
  • PageRecord: A loaded page from the database

Revision Confusion

  • Null edit - edit with nothing that triggers a page to be re-parsed, so may change content but don’t do a revision (i.e: a transcluded template was updated)
  • Null revision - create a revision with the exact same content as before, so no update, but just to put a marker into the history (i.e: restoring the history)
  • PageUpdater is a builder that does page updates. DerivedPageDataUpdater is the state of the page update from the page update builder and a service that executes updates (although we want to split them)
  • RevisionRecord is how revisions are stored for a page
    • Slots are what specific sections have been saved

When Does Parsing Happen?

  • MediaWiki uses the roundtrip time from saving a page and redirecting the user to actually render the page
  • We have a lot of mechanisms to defer the parsing
  • But in reality, certain things have to happen before you save the page
    • For example, hooks that look at what links are being added, so you have to parse the page to get them
  • Parsing always happen before saving, even though MediaWiki is designed to do it afterwards
  • If parsing is triggered, we make sure to reuse the parsing output so we don’t parse again, but sometimes it doesn’t work
    • Pages can render their own revision id, which isn’t known until it’s save so we’d have to re-render it again
  • The current way to avoid re-rendering is weird state management in WikiPage


  • You can play with MediaWiki objects and functions with the maintenance/shell.php script (docs)
  • If MediaWiki can't find a newly-created class, run maintenance/generateLocalAutoload.php
  • When modifying extensions, pay attention to any extension under MediaWiki's Language Extension Bundle, as they must be backwards compatible to at least two stable MediaWiki releases

Useful Links



  • If you're using XDebug, remember to export XDEBUG_SESSION=1 to enable step debugging for tests
  • You can filter PHP tests using --filter and then part of some testing function
  • Use TestingAccessWrapper to access private and protected methods
  • Data providers can return either an array or yield values
  • Data providers are run before the fake databases are set up, so don't use any code that hits the database (i.e: Title::newFromText)
  • Remember to add the @group Database tag to the top comment block of any MediaWikiIntegrationTestCase tests if you're using the database
  • MediaWikiUnitTestCase does not have access to MediaWikiServices but you can use DummyServicesTrait
  • Mocking static methods is a pain in the ass so try to avoid it
  • If you're testing an extension and it throws an error saying it requires some class within the extension, remember to register the extension in LocalSettings.php
  • If you're testing an extension and it throws an error saying it can't find a database, remember to run the maintenance/update.php script after registering the extension
  • If you're testing something and it throws an error saying something about a 'poolwiki' database, you have WikiBase installed and copied the example settings so you can just comment all that stuff out :P


  • For Javascript API testing, the secret key you need in .api-testing-config.json can be set in LocalSettings.php with the variable $wgSecretKey
  • If you're using mediawiki-docker-dev (note: different from mediawiki-docker), everything is already set for you and the .api-testing-config.json is:
	"base_uri": "",
	"main_page": "Main_Page",
	"root_user": {
		"name": "Admin",
		"password": "dockerpass"
	"secret_key": "a5dca55190e1c3927e098c317dd74e85c7eced36f959275114773b188fbabdbc",
	"extra_parameters": {
		"xdebug_session": "PHPSTORM"