Visual Studio Code + WSL: Running PHP Intellisense from Alpine Linux on Windows 10

Callback Insanity
13 min readJan 28, 2020

--

Photo by Valentin B. Kremer on Unsplash

Today I open my Visual Studio Code editor, on Windows 10, and it complained that it could not find PHP.

I know that I do not have PHP installed on Windows.

Nor am I remotely interested in doing it.

Not now. Today. Yesterday. Tomorrow… Ever. You get the point.

But it’d be nice to have PHP auto-complete in Visual Studio Code. I need that. Want that.

Visual Studio Code says: “point me to the path where you have PHP installed”. That is, the PHP binary.

Well. Recently I tried Microsoft’s Remote Containers extension for VSC, and while I was impressed at some of the capabilities that it brings to the table, the experience felt … bloated. Without presenting any proof other than anecdotal evidence, my Docker containers went from being svelte in their memory consumption to feeling heavy and slow, as experienced via both the browser and docker stats .

So today I decided to take a different tack.

And It all starts here:

Is it possible to use wsl bash php for php.validate.executablePath ?

TL;DR: yes — it is possible to use Windows Subsystem for Linux for Visual Studio Code development. After all, they’re both Microsoft projects.

You can read an introductory article about that happy marriage here.

Currently I use WSL Ubuntu to run my Docker Compose workloads.

My Docker daemon itself is running on Docker for Windows, to which WSL Ubuntu connects to.

In order to establish separation of concerns, I am keeping my WSL Ubuntu clean of any project dependencies, lest I run into any conflicts with the Docker Compose client itself.

Therefore, in today’s project I am going to attempt installing PHP inside WSL Alpine.

I already downloaded and installed Alpine WSL from the Windows Store, so you can go there to read on how to do that if you need to, I’m not covering WSL install here.

So anyways, I started Dockerizing …

I open Visual Studio Code “inside” WSL: Alpine, then install PHP inside Alpine:

Installing php7 inside Alpine

These are the commands for installing PHP in Alpine:

DESKTOP-P7TOUMF:~$ apk add --no-cache php7DESKTOP-P7TOUMF:~$ which php
/usr/bin/php
DESKTOP-P7TOUMF:~$ php --version
PHP 7.3.14 (cli) (built: Jan 27 2020 21:55:23) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.14, Copyright (c) 1998-2018 Zend Technologies

After restarting Visual Studio Code, I notice this little popup on the bottom right-hand corner of the editor:

The error:

The PHP Language Server server crashed 5 times in the last 3 minutes. The server will not be restarted.

Here are two related Github issues:

Which mention checking the developer tools console inside VSC. But how do I check the developer tools:

Screenshot:

Since VSC is developed using electron, it shouldn’t be surprising that a Chrome Developer Tools window opens:

Is this relevant? Will it show PHP errors related to VSC extensions?

Clicking on the Console tab of the dev tools window reveals the answer:

YASSS. I can see which errors PHP is throwing. Seeing errors gets me excited, it’s a good omen. If I see an error, then it means I can research and fix it. If it fails and I don’t see error, well…

There’s quite a few laundry list here of things to fix:

But nothing too spooky, not for me anyways.

Since this is a fresh WSL Alpine install running on Windows 10, it looks like we have some housekeeping to do. Or rather, housewarming party.

Here’s the JSON error, for the bots:

[Extension Host] PHP Language Server: CRITICAL  Error: Call to undefined function AdvancedJsonRpc\json_decode() in /home/alpine/.vscode-server/extensions/felixfbecker.php-intellisense-2.3.14/vendor/felixfbecker/advanced-json-rpc/lib/Message.php:26
Stack trace:

I head over to the Amazon.com or Alibaba (I’ve never used Alibaba) of Alpine packages:

This should satisfy the JSON dependency:

php7-json

On my beautiful, integrated VSC terminal, which is connected to the WSL Alpine distro, I can easily add the package:

The VSC terminal on the bottom left … is an ash shell inside Alpine WSL !!!
Added the php7-json extension, right from inside VSC. No need to open cmd.exe, terminal.app, or whathaveyou

The command is

sudo apk add --no-cache php7-json

Next up on the install list, is XDebug:

Given that today is 2020–01–28, that build date of 2020–01–16 is refreshingly … more fresh than Fresh Prince. It feels good to be using Alpine in 2020.

The command here is:

sudo apk add --no-cache php7-pecl-xdebug

The third thing VSC is babbling about is a missing Git install.

Here’s the package:

The build date of 2020–01–14 is also super fresh. Nice!

The command I use is:

sudo apk add --no-cache git

With the housekeeping done in Alpine, using the VSC terminal, I close the Dev Tools window, and reload VSC. To do that, press F1 , the search for reload window :

F1. Type reload window. The select Reload Window to restart VSC.

Partial success! The previous notices seem to be gone. But now there’s a new one:

Error: Call to undefined function token_get_all() in /home/alpine/.vscode-server/extensions/felixfbecker.php-intellisense-2.3.14/vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php:145
Stack trace:
#0 /home/alpine/.vscode-server/extensions/felixfbecker.php-intellisense-2.3.14/vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php(125): phpDocumentor\Reflection\Types\ContextFactory->createForNamespace('LanguageServer', '<?php\ndeclare(s...')
...

A Google search for this

Call to undefined function token_get_all()

Leads to a Drupal post. Oh the sweet irony. I am using my stack for Drupal, after all:

Seems related to some PHP tokenizer extension. There’s an Alpine module for that.

By now you should know what’s the drill. This time I’m skipping the --no-cache flag on the apk add command because this is a local installation of Alpine, and not a Docker container where I’m installing packages:

sudo apk add php7-tokenizer

F1. Reload Window. No popups from the PHP extension!

Let’s check the logs, shall we?

F1. Toggle Developer Tools.

Look Ma’, no errors!

Well, not quite. Looks like XDebug is still needed locally? That’s odd though. Because I can XDebug PHP running in containers using VSC, without having PHP installed on Windows (and XDebug, by extension).

Just out of curiosity, what happened when I installed the XDebug extension in Alpine:

DESKTOP-P7TOUMF:/mnt/c/Users/richa/Sites/app$ php -i | grep xdebug
/etc/php7/conf.d/xdebug.ini
DESKTOP-P7TOUMF:/mnt/c/Users/richa/Sites/app$ cat /etc/php7/conf.d/xdebug.ini
; Uncomment to enable this extension.
;zend_extension=xdebug.so

So it’s installed, but not enabled.

From the VSC integrated terminal:

sudo vi /etc/php7/conf.d/xdebug.ini

Then, uncomment the extension:

F1. Reload Window. These are the results:

PHP Language Server: DEBUG     Checking PHPLS_ALLOW_XDEBUG
DEBUG Restarted (47 ms). The xdebug extension is not loaded

This seems to be related, even though there’s no reason to believe any of the commentariat over at Github are using an exotic WSL setup, not two years ago anyways:

I should have mentioned this earlier, but my current settings.json for VSC have the following added to them:

"php.memoryLimit": "2G",
"php.executablePath": "/usr/bin/php",

Create launch.json

This is a fresh environment, so there is no Git setup, or launch.json for VSC, yet.

To create one properly I can copy pasta it from any other project, but that would defeat the purpose of this story. So to create it properly:

Then select PHP

That creates a stock .vscode/launch.json file:

With PHP and PHP dependencies installed in Alpine Linux WSL, Visual Studio Code ready to launch with launch.json (see what I did there?) configured, and Docker for Windows already configured with custom container stack, it’s time to bring the bacon home.

Bringin’ The Bacon Home

In my local setup

  • Docker Compose is used to launch my PHP stack running on Docker for Windows.
  • I could as well connect my Alpine WSL to Docker for Windows, but I haven’t done that yet.
  • Currently Ubuntu WSL is connected to Docker for Windows, so I manage my containers from there.
  • PHP for Visual Studio Code’s intellisense is not being run from Windows. Or Mac. Ever. I always leave the host unpolluted from binaries. Hygiene. Now PHP is being run from WSL Alpine, with the whole purpose of feeding VSC’s intellisense functions.

I check from WSL Ubuntu that my stack is up and running:

Check that the stack is actually working:

In my Visual Studio Code — WSL Alpine window — I have my Drupal application.

I’m going to set up a breakpoint in sites/default/settings.php because I know it always get hit as part of the Drupal bootstrap process, barring anonymous user or caching use cases, which we’re not dealing with here.

RED DOT: Breakpoint at Line 91 of sites/default/settings.php

With those pre-flight checks done, press F5 in Visual Studio Code to initiate a debugging session. The debugging view shows up:

And the pause button is active, indicating we’re waiting for a connection/request to happen:

Suspense …

After reloading localhost on the browser, nothing happens…

Using Visual Studio Code — WSL requires revisiting some assumptions about xdebug configuration.

WSL is different. It’s not really the native Windows host running Visual Studio Code, from a networking perspective. You see, xdebug needs to know where it’s connecting to when debugging.

Usually I have my xdebug remote_host parameter pointing to host.docker.internal when using Docker for Windows/Mac.

But when running Visual Studio Code, as a WSL window, that’s not where the xdebug client is located. The xdebug client is now not located on the Windows or Mac host. It’s located inside the Windows Subsystem for Linux — in it’s own little world. At least from a networking perspective.

So I need to give xdebug on the server (Docker for Windows) the phone number, or instagram handle, so to speak — of the xdebug client on Visual Studio Code (Windows Subsystem for Linux, Alpine Distro).

If I check my docker compose logs for the PHP FPM service, I get likely confirmation of this theory:

Since I pipe my xdebug configuration from Docker Compose’s YAML files into the PHP container itself, all I need to do is adjust my docker compose configs.

From:

XDEBUG_REMOTE_HOST: host.docker.internal

To:

XDEBUG_REMOTE_HOST: [Windows WSL Alpine address here]

Here’s a good article I found detailing some of that process:

That article contains a lot of technical goodies related to Windows, xdebug, VSC, and WSL 2. It may be worth saving it in some capacity other than a bookmark in case it is lost to Time. Or the Internets.

If you set xdebug.connect_back to 1, then you get something like this:

wsl@DESKTOP-P7TOUMF:/c/Users/richa/Sites/localenv$ docker-compose logs -f php-fpm
Attaching to localenv_php-fpm_1
php-fpm_1 | [28-Jan-2020 09:08:48] NOTICE: fpm is running, pid 1
php-fpm_1 | [28-Jan-2020 09:08:48] NOTICE: ready to handle connections
php-fpm_1 | [28-Jan-2020 09:10:11] WARNING: [pool docker] child 6 said into stdout: "[6] Log opened at 2020-01-28 09:10:11"
php-fpm_1 | [28-Jan-2020 09:10:11] WARNING: [pool docker] child 6 said into stdout: "[6] I: Checking remote connect back address."
php-fpm_1 | [28-Jan-2020 09:10:11] WARNING: [pool docker] child 6 said into stdout: "[6] I: Checking header 'HTTP_X_FORWARDED_FOR'."
php-fpm_1 | [28-Jan-2020 09:10:11] WARNING: [pool docker] child 6 said into stdout: "[6] I: Checking header 'REMOTE_ADDR'."
php-fpm_1 | [28-Jan-2020 09:10:11] WARNING: [pool docker] child 6 said into stdout: "[6] I: Remote address found, connecting to 172.19.0.1:9000."
php-fpm_1 | [28-Jan-2020 09:10:11] WARNING: [pool docker] child 6 said into stdout: "[6] W: Creating socket for '172.19.0.1:9000', poll success, but error: Operation in progress (29)."

Because xdebug attempts to connect to the VM running the Docker Desktop program, in this case 172.19.0.1 .

Which is why I have it set to 0 on my docker compose configuration, along with a healthy helping of comments to warn the weary traveler:

If I look at my local networking stack for Windows, and Windows WSL, I see that the WiFi adapter address is shared in what looks like a bridge configuration. But the WSL contains an ethernet address while Windows does not. I need to talk to the WSL ethernet address.

WSL on left. Windows on right.

To test that it works, I can use telnet.

I don’t have telnet installed on my Windows machine. But it’s installed on WSL Ubuntu. So I can use that to ping WSL Alpine, from where the VSC xdebug client is running, and see if it’s responding:

No telnet for you on windows (right). But WSL Ubuntu (left) says WSL Alpine says yerrrrr!

So if WSL Ubuntu can talk to WSL Alpine on 192.168.201.33:9000 via telnet, then should xdebug running inside a container, inside Docker for Windows.

To do that, I change my xdebug.remote_host setting to the WSL Alpine address of 192.168.201.33 , and restart my Docker stack:

Changed xdebug.remote_host from my compose file, because that’s how we roll.

Restart docker compose stack, and listen for php-fpm logs:

Initiate debug session at Visual Studio Code (waiting for connection):

Press F5 to initiate debug session

Then visit localhost on my browser to trigger the xdebug debugging session:

Now Visual Studio Code’s xdebug client is successfully talking to the xdebug module of Docker for Windows’ PHP-FPM container:

About that error message tho.

Unable to open 'index.php': Unable to read file (Error: File not found (vscode-remote://wsl+alpine/mnt/c/Users/richa/Sites/app/app/web/index.php)).

I forgot that on this fresh, new Alpine WSL environment that I have — I have yet to setup the automount configuration located under /etc/wsl.conf . This means that paths such as /mnt/c/Users show up under WSL instead of /c/Users/etc , which Docker Desktop expects.

Here is my /etc/wsl.conf :

[automount]                 
root = /
enabled = true
options = "metadata,umask=22,fmask=11"

While this doesn’t necessarily means it’s the root cause for that file not found error, I edit wsl.conf and restart my Windows machine, nonetheless.

One of the things that jumped at me from the error message is the duplicate app/app path of the file not found.

Currently my launch.json has the following:

"pathMappings": {
"/app": "${workspaceFolder}/app"
},

Modifying it to:

"pathMappings": {
"/app": "${workspaceFolder}"
},

and re-initiating the debug session resolved the issue.

It’s Alive !!!

Here’s two screenshots of Visual Studio Code debugging Drupal 8 in all it’s glory:

I have my xdebug set to break on the first line of code for testing. This is the first line of code in Drupal.
This is the breakpoint I set in settings.php, getting hit successfully !

Notice the little yellow bar at the bottom of VSC saying that this is running from a WSL: Alpine instance:

And a closer look at the call stack:

The After Action Report (AAR)

Sustain

  • You can run PHP intellisense on Windows without having to install any crap on your native environment.
  • When you install dependencies on your host, you’re bound for trouble, such as: what happens when you need separate versions of these dependencies? WSL on Windows elegantly overcomes this dilemma by providing a safe-space to install the PHP binaries, in this case WSL Alpine.
  • You can point Visual Studio Code to PHP binaries located on WSL. It does require opening a separate dedicated window that points to WSL.

Improve

  • It would be good to have a host.docker.internal equivalent on WSL.
  • Getting and specifying a particular address for xdebug to connect to, as in xdebug.remote_host = 192.168.201.* is finicky. Because this address could potentially change when rebooting the Windows machine, upgrading any of the WSL distros, or simply between different Windows clients or builds.
  • I could always provide more links and explanations as to the rationale behind a particular configuration, architecture or technical decision of mine — I acknowledge this. Such as to why is my path mappings to /app that way. Or explain more what is WSL, etc. But then I would spend more time writing than engineering and that’s not my objective.

I try to document as much as I can, as I go. Mostly for my future self, but also for the potential benefit of hapless technically masochistic souls. Either way, I hope this was helpful. I know it was productive for me!

--

--

Callback Insanity

Organic, fair-sourced DevOps and Full-Stack things. This is a BYOB Establishment — Bring Your Own hipster Beard.