Luke Carrier

How I do Moodle development

Published 2 years ago

There's been a lot of discussion in the #moodle IRC channel recently about different development setups. I've recently been on a plugin development drive, which has involved a great deal of testing across different Moodle versions. Whilst I was polishing these plugins for release, I decided to invest a little time in my development setup.

The prerequisites

My complete configuration is available on GitHub in Chef format. To summarise:

  • Fedora 23 (with SELinux enabled)
  • MySQL (with root credentials stored in ~mysql/.my.cnf for convenience)
  • PostgreSQL, with a mostly default configuration aside from md5 authentication for per-application users
  • Apache HTTPd 2.4.x with mod_userdir, PHP 5.6.x

I keep my source code in ~/Code. Since Apache serves sites out of my ~/Sites directory, I symlink each project's public directory into this directory:

# in ~/Code/LukeCarrier/Moodle
$ ln -s "$PWD" ~/Sites/LukeCarrier-Moodle

Setting up the file structure

Unlike many other configurations I've seen, I tend to avoid duplicating the Moodle source directory. I keep one copy of the Moodle git repository as src and switch between branches as necessary. Each Moodle branch has its own data directory and database, so I'm free to work with as many Moodle series as I need to.

In practice, the files look a little like this:

$ tree -L 1
├── data-19
├── data-20
├── data-21
├── data-22
├── data-23
├── data-24
├── data-25
├── data-26
├── data-27
├── data-master
├── LukeCarrier-Moodle.sublime-project
├── LukeCarrier-Moodle.sublime-workspace
└── src

11 directories, 2 files

Satisfying SELinux

If you're not using Fedora, you can likely skip this.

By default, user home directories are off-limits to the Apache process. This is easy to fix -- we just have to assign a special SELinux context to the data directories and files contained within them. This is fairly easy to set up for all the series:

$ for f in $(sudo semanage fcontext --list | grep "$PWD" | awk '{print $1}'); do \
      sudo semanage fcontext --delete "$f"; \

$ for b in master 27 28 29 30; do \
      sudo semanage fcontext -a -t httpd_cache_t "$PWD/data-$b(/.*)?"; \

$ sudo restorecon -Rv .

Setting up the databases

Each site has its own MySQL and PostgreSQL databases. I've taken to working this way as it allows me to catch accidental usage of reserved SQL keywords and proprietary syntax far more easily.


mysqladmin doesn't really have any notable features, so we'll write our queries ourselves:

$ sudo -u mysql mysql -e "CREATE USER 'luke_moodle'@'localhost' IDENTIFIED BY 'nope'"

$ for b in master 27 28 29 30; do \
      sudo -u mysql mysql -e "CREATE DATABASE luke_moodle_$b CHARACTER SET utf8"; \
      sudo -u mysql mysql -e "GRANT ALL PRIVILEGES ON luke_moodle_$b.* TO 'luke_moodle'@'localhost'"; \


With Postgres, we create our Moodle user and make them the owner of all of the databases:

$ sudo -u postgres createuser -P luke_moodle

$ for b in master 27 28 29 30; do \
      sudo -u postgres createdb -E utf8 -O luke_moodle luke_moodle_$b; \

Satisfying SELinux, again

By default, httpd can't connect to network servers. There's a one-liner to enable it, though:

$ sudo setsebool -P httpd_can_network_connect_db 1

Bringing it all together in config.php

The last couple of changes I make are to the Moodle configuration file. They bring all of the above configuration together to make switching between branches a little more seamless and simplify sharing local sites with my colleagues in the office.

Switching between branches

First, I define a function at the top of the file. This function will parse the Moodle branch from git's HEAD file, which contains a named reference to the current branch.

function local_git_moodle_series() {
    $headcontents = file_get_contents(__DIR__ . '/.git/HEAD');

    preg_match('/MOODLE_([0-9]+)_STABLE$/', $headcontents, $seriesmatches);
    preg_match('/([a-zA-Z0-9]+)$/',         $headcontents, $rawmatches);

    return (count($seriesmatches) === 2) ? $seriesmatches[1] : $rawmatches[1];

Once defined, I assign this value to $branch, and substitute it in to the dbname and dataroot options. In the interests of safety, I unset() this variable before the require_once at the bottom of the file.

Sharing local sites

Moodle's wwwroot option makes perfect sense on a production site, but it's not really necessary locally when you're testing. I determine the server's IP address from the PHP $_SERVER superglobal:

$serveraddr = array_key_exists('SERVER_ADDR', $_SERVER)
        ? $_SERVER['SERVER_ADDR'] : 'localhost';

...and then I substitute that value into wwwroot. Note that PHP's CLI SAPI doesn't define the SERVER_ADDR index, so we use the ternary operator to default to "localhost" if it's not set.

That's all, folks

I hope this proves useful for any Moodle developers looking to simplify their plugin development and testing cycle! I'd be happy to hear any feedback on the above.