Skip to main content
Semantic MediaWiki follows the MediaWiki coding conventions and layers additional conventions on top to keep the codebase testable, maintainable, and consistent. These rules apply to all code in the src/ directory. Code in includes/ is legacy and does not necessarily comply.

File and folder layout

PSR-4 in src/

All classes under src/ follow PSR-4 autoloading. The top-level namespace is SMW; every component lives in a sub-namespace matching its directory path.

Legacy includes/

The includes/ directory contains legacy code that pre-dates the PSR-4 layout. As classes gain sufficient test coverage they are migrated to src/. The long-term goal is to eliminate includes/ entirely.

Namespace structure

// Top-level namespace
namespace SMW;

// Component sub-namespaces (examples)
namespace SMW\SQLStore;
namespace SMW\Elastic;
namespace SMW\SPARQLStore;
namespace SMW\Query;
namespace SMW\DataValues;
namespace SMW\DataItems;
namespace SMW\MediaWiki;   // MediaWiki object accessors live here
namespace SMW\Services;
Object interaction with MediaWiki classes (e.g. Title, WikiPage, ParserOutput) must go through accessors in the SMW\MediaWiki namespace rather than being scattered across the codebase.

PHP naming conventions

ConstructConventionExample
ClassesCamelCase in a namespace starting with SMWSMW\Query\ResultPrinter
Methods and variablescamelCase starting with a lowercase lettergetPropertyValues(), $dataItem
Global functionssmwf prefixsmwfNormalTitleDBKey()
Global variablessmwg prefix$smwgFixedProperties
Global constantsALL_CAPS with SMW_ prefixSMW_VERSION
Class constantsALL_CAPS, no prefix requiredDataItem::TYPE_NUMBER

@private annotation

A class or method annotated with @private is restricted to internal use within SMW core. Even if the method has public visibility, external code must not rely on it — its signature or existence may change in any release.

Code layout

Method ordering

Methods inside a class are ordered by visibility to make the public contract immediately visible:
class ExampleService {

    // 1. public methods first
    public function doSomething(): void {}

    // 2. protected methods second
    protected function doSomethingInternal(): void {}

    // 3. private methods last
    private function helper(): void {}

}

General rules

  • All files must be UTF-8 encoded with UNIX line endings.
  • Do not use a closing ?> tag.
  • Indent with tabs, not spaces.
  • Always wrap single-statement blocks in { }.
  • Use && and || for logical operators, not and and or.
  • Write true, false, and null in lowercase.
  • Declare class members private by default; use protected only when sub-class sharing is needed; use public to expose a method as part of the API.

Dependency injection

SMW strongly prefers dependency injection over inheritance and service location inside business logic.
// Preferred — dependencies injected via constructor
class QueryProcessor {

    public function __construct(
        private Store $store,
        private QueryParser $parser,
    ) {}

}
// Avoid — pulling dependencies from a global service locator inside a class
class QueryProcessor {

    public function process( SMWQuery $query ) {
        // Avoid this pattern in non-factory code
        $store = ApplicationFactory::getInstance()->getStore();
    }

}

Factory pattern for instance creation

All new Foo(...) calls should be delegated to a factory service. Factories must not use conditional logic to decide which class to instantiate — conditionals belong in a builder or service locator.
// Factory: creates one type of object cleanly
class DataItemFactory {

    public function newDIWikiPage( string $dbKey, int $namespace ): DIWikiPage {
        return new DIWikiPage( $dbKey, $namespace );
    }

}
Use SMW\Services\ServicesFactory (formerly ApplicationFactory) as the central service locator for obtaining shared services. Do not instantiate services directly in application code.

Type hinting

Type hints must be used consistently on all new code, including method parameters, return types, and property declarations where supported by the minimum PHP version.
public function getPropertyValues(
    DIWikiPage $subject,
    DIProperty $property,
    ?RequestOptions $requestOptions = null
): array;
Type hints are especially important in base classes and interfaces — without them, subclasses cannot add their own type hints, breaking the class contract.

Source documentation

Every class and public method must have a docblock using Doxygen notation:
/**
 * Returns all values stored for the given property on the given subject.
 *
 * @since 3.0
 *
 * @param DIWikiPage $subject
 * @param DIProperty $property
 *
 * @return DataItem[]
 */
public function getPropertyValues(
    DIWikiPage $subject,
    DIProperty $property
): array;
  • Use @since to record the version in which a method or hook was introduced.
  • Use @todo and @bug inline to track known issues without losing context.
  • Remove unused debug statements, variables, and functions before submitting a PR.

Testing requirements

All tests must pass before changes can be merged. There are no exceptions.
Unit tests cover a single class or method in isolation. They must not connect to a database or external service.
# Run unit tests only
php vendor/bin/phpunit --group unit
Every new class and every new public method requires a corresponding unit test.

Running the full test suite locally

make composer-test

Git workflow

1

Target the master branch

All development happens against master. Backports to maintenance branches are done by cherry-picking commits after they have merged into master.
2

Send a first PR

Before submitting substantive changes, send a PR with the subject [first pr] to verify your git setup and that you can replicate changes against master.
3

Prepare your PR

Run make composer-test locally and confirm all tests pass. If your PR introduces new behaviour, include tests that cover it before requesting review.
4

Watch CI

Every PR triggers GitHub Actions across multiple MediaWiki and PHP versions. Review the output of failing jobs to identify the cause before pushing a fix.
5

Rebase before merge

Rebase your branch on the latest master before requesting a final review. Do not merge master into your branch — use rebase to keep a clean linear history.

JavaScript

JavaScript modules are registered with ResourceLoader via res/Resources.php. Global variables must carry the smw prefix. Use JSHint to catch potential errors before committing.

Build docs developers (and LLMs) love