Enabling a PHP extension in Nix

Following up on Nix flake with Node and PHP, I needed to enable an extension (in this case, MongoDB). The end result is simple enough, but getting there was a little tricky.

A complete flake

Let me start by posting my complete flake.nix. This supports a composer-based project that has mongodb/mongodb as a constraint.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
phps.url = "github:fossar/nix-phps";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, flake-utils, phps }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
php74 = (phps.packages.${system}.php74.buildEnv {
extensions = ({ enabled, all }: enabled ++ (with all; [
mongodb
]));
});
in
{
devShell = pkgs.mkShell {
buildInputs = [
php74
php74.packages.composer
];
shellHook = ''
php -v
'';
};
}
);
}

Inputs

I’m defining three inputs: nixpkgs, phps, and flake-utils. The NixOS manual calls inputs dependencies; I think of them as package sources:

1
2
3
4
5
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
phps.url = "github:fossar/nix-phps";
flake-utils.url = "github:numtide/flake-utils";
};

These, respectively, are the standard NixOS package repository, the nix-phps repository for older PHP versions, and flake-utils. The latter isn’t essential, but it let me simplify my output syntax.

Outputs

The first thing your output section has to do is accept all the inputs as arguments; they’re passed regardless, and you’ll get an error if you don’t include them:

1
outputs = { self, nixpkgs, flake-utils, phps }:

This can happen even if you don’t use them for some reason. Sample error:

1
error: function 'outputs' called with unexpected argument 'nixpkgs'

Let…in

There’s a let…in block at the heart of the output declaration. This lets me assign values to names which can then be used within the scope of the block:

1
2
3
4
5
6
7
8
9
10
 outputs = { self, nixpkgs, flake-utils, phps }:
flake-utils.lib.eachDefaultSystem (system:
let
# Assign values to names
in
​ {
# Do stuff with those values
​ }
​ );
}

System

Nix can work crossplatform, but you need to provide some definitions to enable that. This is where flake-utils saves some front matter. This:

1
2
3
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};

…is short-hand for this:

1
2
3
4
5
6
7
8
9
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
"aarch64-linux" # 64-bit ARM Linux
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
pkgs = import nixpkgs { inherit system; };
});

PHP

Something I didn’t understand when I was starting on this is that every expression is self-contained. Put simply, it’s not enough to configure and enable the MongoDB extension for the PHP cli binary; I need to do that for composer as well. Therefore, let’s create a resuable expression for PHP that does that:

1
2
3
4
5
php74 = (phps.packages.${system}.php74.buildEnv {
extensions = ({ enabled, all }: enabled ++ (with all; [
mongodb
]));
});

That gives me a PHP 7.4 definition that includes the MongoDB extension.

Shell

I don’t fully grok everything going under the hood here, but I’ll leave breadcrumbs for myself and anyone else. mkShell is an extension of mkDerivation, so it has all the attributes of the latter plus a few of its own. With buildInputs I’m defining what packages to include; shellHooks are commands to execute when I run nix develop to bring up the shell:

1
2
3
4
5
6
7
8
9
devShell = pkgs.mkShell {
buildInputs = [
php74
php74.packages.composer
];
shellHook = ''
php -v
'';
};

Here, I add both the PHP 7.4 CLI and composer with all default extensions and the MongoDB extension. In the shell hook, I just tell it to run php and output the version when it comes up.