Andrew Welch
Published , updated · 5 min read · RSS Feed
Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.
A Craft CMS Plugin Local Development Environment
This article details a containerized local development environment that you can use to develop, debug, and test Craft CMS plugins with ease
To develop a Craft CMS plugin, you’ll need a local development environment that consists of a Craft CMS install and the associated server requirements to run it.
Usually this consists of a loosely hacked together local development environment using whatever you use for local development.
There’s a better way
Whether you’re new to plugin development, or an experienced pro, the setup provided here will make it easier for you to develop, debug, and test your Craft CMS plugins.
It’s the setup we use for the nystudio107 plugins, too.
If you’re doing custom module development, then the host development environment is your project, as discussed in the Enhancing a Craft CMS 3 Website with a Custom Module article. Regardless, there are several parts of the article you’re reading now (especially the PhpStorm and XDebug sections) that will still apply.
Link What features do we get?
The containerized plugindev local development environment gives us a bunch of features out of the box:
- Craft CMS ^3.6.7 is installed in a portable, cross-platform Docker setup
- A PHP 8.x environment with Imagick and other needed extensions pre-installed
- A prefab database seed for both MySQL and Postgres databases
- The ability to switch between MySQL and Postgres instantly
- Dual production/debug Docker containers for PHP, for improved performance while also supporting XDebug
- Multiple sites for testing
- Prefab content with a “blog” channel, category groups, etc. for testing
- Bare-bones Twig templates for testing frontend functionality
- Both the Craft Commerce and Redactor plugins pre-installed
- Auto-completion of Craft CMS & plugin APIs in Twig as per the Auto-Complete Craft CMS 3 APIs in Twig with PhpStorm article
…and a whole bunch more. The setup is similar to that detailed in the An Annotated Docker Config for Frontend Web Development article, but with pre-seeded content, and a specialized setup for effective plugin development.
Even better, you can easily customize it to suit your specific needs.
So let’s dive in!
Link Setting up plugindev
As discussed in the So You Wanna Make a Craft 3 Plugin? article, we need to set up our host Craft CMS 3 install.
Instead of using whatever local development environment you normally use, we’ll use the nystudio107/plugindev environment by creating a new project:
composer create-project nystudio107/plugindev --no-install
This will create a project named plugindev which is a turnkey Craft CMS install for developing plugins.
Composer will have already created a .env file in the cms/ directory, based off of the provided example.env
We just need to make a few small edits to customize the setup for you:
- Edit the cms/composer.json file and change the line "url": "/Users/andrew/webdev/craft/*", in repostories to point to your local plugin Git repositories
- Edit the docker-composer.yaml file and change the line - /Users/andrew/webdev/craft:/Users/andrew/webdev/craft to point to your local plugin Git repositories
All we’re doing here is telling Composer that we want it to look in our local path Git repositories when looking for the plugins we’re developing, and we’re telling Docker how to map the directory in its container to our local directory.
For me, the /Users/andrew/webdev/craft/ directory has a repository for every one of the nystudio107 plugins. You just need to tell it where your plugin repositories live (keep them all in one directory for simplicity’s sake).
When your host Craft CMS 3 site starts up, Composer will look in your local path repository for Packagist packages first, and create a symlink to your local plugin folder:
> ~/webdev/sites/plugindev/cms/vendor/nystudio107 ls -al ✔
total 0
drwxr-xr-x@ 31 andrew staff 992 Apr 17 17:53 .
drwxr-xr-x@ 66 andrew staff 2112 Apr 17 17:54 ..
lrwxr-xr-x@ 1 andrew staff 40 Apr 17 17:53 craft-connect -> /Users/andrew/webdev/craft/craft-connect
lrwxr-xr-x@ 1 andrew staff 40 Apr 17 17:53 craft-cookies -> /Users/andrew/webdev/craft/craft-cookies
lrwxr-xr-x@ 1 andrew staff 39 Apr 17 17:53 craft-disqus -> /Users/andrew/webdev/craft/craft-disqus
lrwxr-xr-x@ 1 andrew staff 44 Apr 17 17:53 craft-eagerbeaver -> /Users/andrew/webdev/craft/craft-eagerbeaver
lrwxr-xr-x@ 1 andrew staff 46 Apr 17 17:53 craft-emptycoalesce -> /Users/andrew/webdev/craft/craft-emptycoalesce
lrwxr-xr-x@ 1 andrew staff 49 Apr 17 17:53 craft-fastcgicachebust -> /Users/andrew/webdev/craft/craft-fastcgicachebust
lrwxr-xr-x@ 1 andrew staff 46 Apr 17 17:53 craft-imageoptimize -> /Users/andrew/webdev/craft/craft-imageoptimize
lrwxr-xr-x@ 1 andrew staff 52 Apr 17 17:53 craft-imageoptimize-imgix -> /Users/andrew/webdev/craft/craft-imageoptimize-imgix
lrwxr-xr-x@ 1 andrew staff 52 Apr 17 17:53 craft-imageoptimize-sharp -> /Users/andrew/webdev/craft/craft-imageoptimize-sharp
lrwxr-xr-x@ 1 andrew staff 54 Apr 17 17:53 craft-imageoptimize-thumbor -> /Users/andrew/webdev/craft/craft-imageoptimize-thumbor
lrwxr-xr-x@ 1 andrew staff 49 Apr 17 17:53 craft-instantanalytics -> /Users/andrew/webdev/craft/craft-instantanalytics
lrwxr-xr-x@ 1 andrew staff 39 Apr 17 17:53 craft-minify -> /Users/andrew/webdev/craft/craft-minify
lrwxr-xr-x@ 1 andrew staff 42 Apr 17 17:53 craft-pathtools -> /Users/andrew/webdev/craft/craft-pathtools
lrwxr-xr-x@ 1 andrew staff 48 Apr 17 17:53 craft-plugin-manifest -> /Users/andrew/webdev/craft/craft-plugin-manifest
lrwxr-xr-x@ 1 andrew staff 39 Apr 17 17:53 craft-recipe -> /Users/andrew/webdev/craft/craft-recipe
lrwxr-xr-x@ 1 andrew staff 39 Apr 17 17:53 craft-retour -> /Users/andrew/webdev/craft/craft-retour
lrwxr-xr-x@ 1 andrew staff 46 Apr 17 17:53 craft-richvariables -> /Users/andrew/webdev/craft/craft-richvariables
lrwxr-xr-x@ 1 andrew staff 41 Apr 17 17:53 craft-routemap -> /Users/andrew/webdev/craft/craft-routemap
lrwxr-xr-x@ 1 andrew staff 41 Apr 17 17:53 craft-seomatic -> /Users/andrew/webdev/craft/craft-seomatic
lrwxr-xr-x@ 1 andrew staff 40 Apr 17 17:53 craft-similar -> /Users/andrew/webdev/craft/craft-similar
lrwxr-xr-x@ 1 andrew staff 49 Apr 17 17:53 craft-templatecomments -> /Users/andrew/webdev/craft/craft-templatecomments
lrwxr-xr-x@ 1 andrew staff 43 Apr 17 17:53 craft-transcoder -> /Users/andrew/webdev/craft/craft-transcoder
lrwxr-xr-x@ 1 andrew staff 41 Apr 17 17:53 craft-twigpack -> /Users/andrew/webdev/craft/craft-twigpack
lrwxr-xr-x@ 1 andrew staff 45 Apr 17 17:53 craft-twigprofiler -> /Users/andrew/webdev/craft/craft-twigprofiler
lrwxr-xr-x@ 1 andrew staff 42 Apr 17 17:53 craft-typogrify -> /Users/andrew/webdev/craft/craft-typogrify
lrwxr-xr-x@ 1 andrew staff 38 Apr 17 17:53 craft-units -> /Users/andrew/webdev/craft/craft-units
lrwxr-xr-x@ 1 andrew staff 46 Apr 17 17:53 craft-vanillaforums -> /Users/andrew/webdev/craft/craft-vanillaforums
lrwxr-xr-x@ 1 andrew staff 40 Apr 17 17:53 craft-webperf -> /Users/andrew/webdev/craft/craft-webperf
lrwxr-xr-x@ 1 andrew staff 49 Apr 17 17:53 craft-youtubeliveembed -> /Users/andrew/webdev/craft/craft-youtubeliveembed
Now start up the plugindev local development environment with:
make dev
This starts everything up for us as discussed in the Using Make & Makefiles to Automate your Frontend Workflow article.
Subsequent make dev commands will be much faster, but still a little slow because we intentionally do a composer install each time, to keep our dependencies in sync.
Wait until you see the following to indicate that the PHP container is ready:
php_1 | Craft is installed.
php_1 | Applying changes from your project config files ... done
php_1 | [01-Dec-2020 18:38:46] NOTICE: fpm is running, pid 22
php_1 | [01-Dec-2020 18:38:46] NOTICE: ready to handle connections
That’s it, we’re up and running & ready to rock.
To spin down the containers, just type Control-C in the terminal window.
If you want to add your own plugin to the composer.json of the plugindev project, you can either:
- Type make composer require vendorname/pluginname
- Edit the cms/composer.json file directly, then do make composer update
Link Logging in to plugindev
The plugindev website is now up and running at http://localhost:8000 so to log into the CP, you’d use the URL:
http://localhost:8000/admin
You’ll use the following credentials to log in:
User: admin
Password: password
…and you’ll find a full Craft CMS 3 host install ready and waiting for you to do your plugin development.
Link Switching between MySQL & Postgres
The plugindev project supports both MySQL and Postgres out of the box. It spins up a container for each database, and seeds them with a starter db.
To use MySQL (the default) just type:
make mysql
To use Postgres just type:
make postgres
You’ll have to log into each environment once, but after that it’ll remember your logged in/out state regardless of how many times you switch contexts.
Being able to switch between MySQL and Postgres makes it infinitely easier to debug & test your plugins with both database schemas.
It’s supposed to be seamless if you use the Yii2 Query Builder, but the last 10% often requires a bit of special-casing and custom code to support each database schema.
Additionally, we’re using MySQL 8 because it is the most strict, and thus likely to bring out any errors.
Link Plugindev commands
Plugindev uses make and Makefiles as discussed in the Using Make & Makefiles to Automate your Frontend Workflow article.
This makes it easy to provide a simplified API for interacting with the underlying containers:
- make dev — starts up the local dev server listening on http://localhost:8000/
- make clean — shuts down the Docker containers, removes any mounted volumes (including the database), and then rebuilds the containers from scratch
- make mysql — switches the project to use the MySQL database container; just reload the browser
- make postgres — switches the project to use the Postgres database container; just reload the browser
- make update — causes the project to update to the latest Composer dependencies
- make update-clean — completely removes vendor/, then causes the project to update to the latest Composer dependencies
- make composer xxx — runs the composer command passed in, e.g. make composer install in the php container
- make craft xxx — runs the craft console command passed in, e.g. make craft project-config/apply in the php container
Link Dual PHP Containers for Xdebug
By default, all of your requests will be routed through the production PHP container, for speedy local development and testing.
However, there is a second Xdebug PHP container that’s always running too, for the time that you need to use Xdebug on the really tough problems.
What happens is a request comes in, Nginx looks to see if there’s an XDEBUG_SESSION or XDEBUG_PROFILE cookie set. If there’s no cookie, it routes the request to the regular php container.
If however the XDEBUG_SESSION or XDEBUG_PROFILE cookie is set (with any value), it routes the request to the php_xdebug container.
You can set this cookie with a browser extension, your IDE, or via a number of other methods. Here is the Xdebug Helper browser extension for your favorite browsers: Chrome — Firefox — Safari
You can read more about it in the Dual An Annotated Docker Config for Frontend Web Development article, and we’ll talk more about XDebug later in this article as well.
Link Making it your own
While the plugindev environment will work great out of the box for any kind of plugin development, you may want to make it your own by perhaps:
- Having your own plugins pre-installed
- Having your own customized test data in both the MySQL and Postgres databases as Craft CMS content
- Customizations to the templates
- Branding the CP with your own logo and background
- Writing your own onboarding instructions for your team in the README.md
You can easily do this by forking the plugindev repository and making whatever changes you want to it.
Then when you go to create a new local plugin development site you’ll use composer create-project yourvendorname/plugindev --no-install instead to make a new one!
Make it your own
Change the templates in cms/templates/, adjust whatever plugins should be installed in cms/composer.json, etc., etc.
To make database schema and/or content changes, follow these steps:
- Switch to MySQL via make mysql
- Make whatever schema/content changes you need to in the CP
- Dump the database via Utilities → Database Backup
- Replace the database in mysql-db-seed with the new one
- Switch to Postgres via make postgres
- Schema changes will be automatically applied via Project Config, but you’ll need to redo any content changes
- Dump the database via Utilities → Database Backup
- Replace the database in posgres-db-seed with the new one
Then you’ll need to copy your changes to the forked plugindev repository. Remember, we created a new project via composer create-project which isn’t associated with any git repository.
You’ll rarely need to make extensive changes to your own plugindev setup, but you can change as much or as little as you like.
You can see how we do it in the plugindev commit history.
Link Using PhpStorm
While you can use any IDE to do plugin development, I generally recommend PhpStorm if you’re doing any serious amount of PHP coding.
As the So You Wanna Make a Craft 3 Plugin? article discusses, one absolutely amazing thing you can do in PhpStorm is get full auto-completion of the entire craft and Yii2 API!
To do make the magic happen, install the Symfony plugin, then Go to Preferences → Languages & Frameworks → PHP → Symfony and check Enable Plugin for this Project.
You can get this in your Twig templates, too, as per the Auto-Complete Craft CMS 3 APIs in Twig with PhpStorm article
I would also highly recommend installing the Craft Code Style & Craft Inspections from Pixel & Tonic. Also take the time to install the Php Inspections (EA Extended) and Yii2 Inspections plugins. These will all save you an enormous amount of time.
By default, any packages installed in vendor/ will initially be excluded via Preferences → Directories and disclose cms/vendor/nystudio107/ (or whatever vendor directory your plugins are in):
Excluded folders are ignored by code completion, navigation, and inspections, but since these are plugins we’re working on developing, we want them included:
Also although we’re running PHP 8.x in our plugindev setup, since we’re developing for Craft CMS 3.x which requires PHP 7.0, we need to set that in Preferences → PHP so our inspections are correct:
If you’re using Craft CMS 3.6 or later, and know your plugins will never be used on an earlier version, you can set the PHP language level to 7.2 if you like.
And then we’ll also want to tell it how to connect to our remote PHP CLI interpreter running in our Docker container from Preferences → PHP click on the … next to CLI interpreter, then click on + to add a new one, and choose From Docker, Vagrant, VM, WSL, Remote…
Then click on Docker and choose the plugindev_php_xdebug:latest image.
Link XDebug with PhpStorm
If you’ve never used XDebug, it makes debugging PHP code so much easier. You can set breakpoints, inspect values, go forward to backwards through stack traces, and a whole lot more.
As mentioned previously, plugindev uses the dual Docker containers as described in the An Annotated Docker Config for Frontend Web Development article.
This means that all requests will go through the optimized PHP production container by default. But if you enable the XDEBUG_SESSION or XDEBUG_PROFILE cooke via the XDebug Helper browser extension, they’ll be routed to a PHP container running XDebug 3:
What happens is XDebug running in the plugindev Docker container will call out to anything that might be listening, to allow it to establish an XDebug connection on port 9003.
You can then set your breakpoints as you see fit, but you’ll probably see a message like this:
What it’s saying is that it doesn’t know how to translate between the PHP files on your local computer with the PHP files in your remote Docker container.
So we need to set up path mappings to tell it by clicking on the PHP|Servers link. I start with the directory:
The directories on your local computer are on the left, and you can click on the right to add a mapping to where the files exists in the remote Docker container.
In our case, the vendor/ directory is located at /var/www/project/vendor in the Docker container, so add that, and click OK.
Now you’ll probably see something like this:
This is good, because it means PhpStorm / XDebug has paused our application at the breakpoint we set (you’ll need to click on the green ► to run through the breakpoint), but it doesn’t know how to map the directories.
We can fix this by adding a mapping for each plugin:
The directories on your local computer are on the left, and you can click on the right to add a mapping to where the files exists in the remote Docker container.
In our case, the craft-imageoptimize/ directory is located at /Users/andrew/webdev/craft/craft-imageoptimize in the Docker container, so add that, and click OK.
We need to tell it where these directories for our plugins are one by one, because PhpStorm doesn’t crawl the symlinks.
Link XDebug with VScode
To use Xdebug with VSCode install the PHP Debug extension and use the following configuration in your .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"log": true,
"externalConsole": false,
"pathMappings": {
"/var/www/project/cms": "${workspaceRoot}/cms"
},
"ignore": ["**/vendor/**/*.php"]
}
]
}
There may be more to it than this, I’m not terribly familiar with using VSCode for PHP development.
Link Plugindev like a pro
You can use the plugindev environment provided here without modification to develop, debug, and test your plugins easily.
Or you can customize the plugindev environment to make it your own, or just take some of the concepts discussed here, and apply them to your own setup.
Either way, happy plugging!
If you want to see this plugindev environment in action, check out this video on CraftQuest.io!