Andrew Welch · Insights · #craftcms #plugin #Development

Published , updated · 5 min read ·


Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.

A Craft CMS Plugin Local Development Environment

This arti­cle details a con­tainer­ized local devel­op­ment envi­ron­ment that you can use to devel­op, debug, and test Craft CMS plu­g­ins with ease

To devel­op a Craft CMS plu­g­in, you’ll need a local devel­op­ment envi­ron­ment that con­sists of a Craft CMS install and the asso­ci­at­ed serv­er require­ments to run it.

Usu­al­ly this con­sists of a loose­ly hacked togeth­er local devel­op­ment envi­ron­ment using what­ev­er you use for local development.

There’s a better way

Whether you’re new to plu­g­in devel­op­ment, or an expe­ri­enced pro, the set­up pro­vid­ed here will make it eas­i­er for you to devel­op, debug, and test your Craft CMS plugins.

It’s the set­up we use for the nystudio107 plu­g­ins, too.

If you’re doing cus­tom mod­ule devel­op­ment, then the host devel­op­ment envi­ron­ment is your project, as dis­cussed in the Enhanc­ing a Craft CMS 3 Web­site with a Cus­tom Mod­ule arti­cle. Regard­less, there are sev­er­al parts of the arti­cle you’re read­ing now (espe­cial­ly the Php­Storm and XDe­bug sec­tions) that will still apply.

Link What features do we get?

The con­tainer­ized plug­in­dev local devel­op­ment envi­ron­ment gives us a bunch of fea­tures out of the box:

…and a whole bunch more. The set­up is sim­i­lar to that detailed in the An Anno­tat­ed Dock­er Con­fig for Fron­tend Web Devel­op­ment arti­cle, but with pre-seed­ed con­tent, and a spe­cial­ized set­up for effec­tive plu­g­in development.

Even bet­ter, you can eas­i­ly cus­tomize it to suit your spe­cif­ic needs.

So let’s dive in!

Link Setting up plugindev

As dis­cussed in the So You Wan­na Make a Craft 3 Plu­g­in? arti­cle, we need to set up our host Craft CMS 3 install.

Instead of using what­ev­er local devel­op­ment envi­ron­ment you nor­mal­ly use, we’ll use the nystudio107/​plugindev envi­ron­ment by cre­at­ing a new project:

composer create-project nystudio107/plugindev --no-install

This will cre­ate a project named plugindev which is a turnkey Craft CMS install for devel­op­ing plugins.

Com­pos­er will have already cre­at­ed a .env file in the cms/ direc­to­ry, based off of the pro­vid­ed example.env

We just need to make a few small edits to cus­tomize the set­up for you:

  • Edit the cms/composer.json file and change the line "url": "/Users/andrew/webdev/craft/*", in repostories to point to your local plu­g­in 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 plu­g­in Git repositories

All we’re doing here is telling Com­pos­er that we want it to look in our local path Git repos­i­to­ries when look­ing for the plu­g­ins we’re devel­op­ing, and we’re telling Dock­er how to map the direc­to­ry in its con­tain­er to our local directory.

For me, the /Users/andrew/webdev/craft/ direc­to­ry has a repos­i­to­ry for every one of the nystudio107 plu­g­ins. You just need to tell it where your plu­g­in repos­i­to­ries live (keep them all in one direc­to­ry for sim­plic­i­ty’s sake).

When your host Craft CMS 3 site starts up, Com­pos­er will look in your local path repos­i­to­ry for Pack­ag­ist pack­ages first, and cre­ate a sym­link to your local plu­g­in 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 plug­in­dev local devel­op­ment envi­ron­ment with:

make dev

This starts every­thing up for us as dis­cussed in the Using Make & Make­files to Auto­mate your Fron­tend Work­flow arti­cle.

Sub­se­quent make dev com­mands will be much faster, but still a lit­tle slow because we inten­tion­al­ly do a composer install each time, to keep our depen­den­cies in sync.

Wait until you see the fol­low­ing to indi­cate that the PHP con­tain­er 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 run­ning & ready to rock.

To spin down the con­tain­ers, just type Control-C in the ter­mi­nal window.

If you want to add your own plu­g­in to the composer.json of the plug­in­dev project, you can either:

  1. Type make composer require vendorname/pluginname
  2. Edit the cms/composer.json file direct­ly, then do make composer update

Link Logging in to plugindev

The plug­in­dev web­site is now up and run­ning at http://localhost:8000 so to log into the CP, you’d use the URL:

http://localhost:8000/admin

Log­ging into the CP

You’ll use the fol­low­ing cre­den­tials to log in:

User: admin

Pass­word: password

…and you’ll find a full Craft CMS 3 host install ready and wait­ing for you to do your plu­g­in development.

Link Switching between MySQL & Postgres

The plug­in­dev project sup­ports both MySQL and Post­gres out of the box. It spins up a con­tain­er for each data­base, and seeds them with a starter db.

To use MySQL (the default) just type:

make mysql

Switch to MySQL

To use Post­gres just type:

make postgres

Switch to Postgres

You’ll have to log into each envi­ron­ment once, but after that it’ll remem­ber your logged in/​out state regard­less of how many times you switch contexts.

Being able to switch between MySQL and Post­gres makes it infi­nite­ly eas­i­er to debug & test your plu­g­ins with both data­base schemas.

It’s sup­posed to be seam­less if you use the Yii2 Query Builder, but the last 10% often requires a bit of spe­cial-cas­ing and cus­tom code to sup­port each data­base schema.

Addi­tion­al­ly, we’re using MySQL 8 because it is the most strict, and thus like­ly to bring out any errors.

Link Plugindev commands

Plug­in­dev uses make and Makefiles as dis­cussed in the Using Make & Make­files to Auto­mate your Fron­tend Work­flow arti­cle.

This makes it easy to pro­vide a sim­pli­fied API for inter­act­ing with the under­ly­ing containers:

  • make dev — starts up the local dev serv­er lis­ten­ing on http://localhost:8000/
  • make clean — shuts down the Dock­er con­tain­ers, removes any mount­ed vol­umes (includ­ing the data­base), and then rebuilds the con­tain­ers from scratch
  • make mysql — switch­es the project to use the MySQL data­base con­tain­er; just reload the browser
  • make postgres — switch­es the project to use the Post­gres data­base con­tain­er; just reload the browser
  • make update — caus­es the project to update to the lat­est Com­pos­er dependencies
  • make update-clean — com­plete­ly removes vendor/, then caus­es the project to update to the lat­est Com­pos­er dependencies
  • make composer xxx — runs the composer com­mand passed in, e.g. make composer install in the php container
  • make craft xxx — runs the craft con­sole com­mand 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 rout­ed through the pro­duc­tion PHP con­tain­er, for speedy local devel­op­ment and testing.

How­ev­er, there is a sec­ond Xde­bug PHP con­tain­er that’s always run­ning too, for the time that you need to use Xde­bug on the real­ly tough problems.

What hap­pens is a request comes in, Nginx looks to see if there’s an XDEBUG_SESSION or XDEBUG_PROFILE cook­ie set. If there’s no cook­ie, it routes the request to the reg­u­lar php container.

If how­ev­er the XDEBUG_SESSION or XDEBUG_PROFILE cook­ie is set (with any val­ue), it routes the request to the php_xdebug container.

You can set this cook­ie with a brows­er exten­sion, your IDE, or via a num­ber of oth­er meth­ods. Here is the Xde­bug Helper brows­er exten­sion for your favorite browsers: Chrome — Fire­fox — Safari

XDe­bug Helper brows­er extension

You can read more about it in the Dual An Anno­tat­ed Dock­er Con­fig for Fron­tend Web Devel­op­ment arti­cle, and we’ll talk more about XDe­bug lat­er in this arti­cle as well.

Link Making it your own

While the plug­in­dev envi­ron­ment will work great out of the box for any kind of plu­g­in devel­op­ment, you may want to make it your own by perhaps:

  • Hav­ing your own plu­g­ins pre-installed
  • Hav­ing your own cus­tomized test data in both the MySQL and Post­gres data­bas­es as Craft CMS content
  • Cus­tomiza­tions to the templates
  • Brand­ing the CP with your own logo and background
  • Writ­ing your own onboard­ing instruc­tions for your team in the README.md

You can eas­i­ly do this by fork­ing the plug­in­dev repos­i­to­ry and mak­ing what­ev­er changes you want to it.

Then when you go to cre­ate a new local plu­g­in devel­op­ment site you’ll use composer create-project yourvendorname/plugindev --no-install instead to make a new one!

Make it your own

Change the tem­plates in cms/templates/, adjust what­ev­er plu­g­ins should be installed in cms/composer.json, etc., etc.

To make data­base schema and/​or con­tent changes, fol­low these steps:

  • Switch to MySQL via make mysql
  • Make what­ev­er schema/​content changes you need to in the CP
  • Dump the data­base via Util­i­ties → Data­base Backup
  • Replace the data­base in mysql-db-seed with the new one
  • Switch to Post­gres via make postgres
  • Schema changes will be auto­mat­i­cal­ly applied via Project Con­fig, but you’ll need to redo any con­tent changes
  • Dump the data­base via Util­i­ties → Data­base Backup
  • Replace the data­base in posgres-db-seed with the new one

Then you’ll need to copy your changes to the forked plug­in­dev repos­i­to­ry. Remem­ber, we cre­at­ed a new project via composer create-project which isn’t asso­ci­at­ed with any git repository.

You’ll rarely need to make exten­sive changes to your own plug­in­dev set­up, but you can change as much or as lit­tle as you like.

You can see how we do it in the plug­in­dev com­mit his­to­ry.

Link Using PhpStorm

While you can use any IDE to do plu­g­in devel­op­ment, I gen­er­al­ly rec­om­mend Php­Storm if you’re doing any seri­ous amount of PHP coding.

As the So You Wan­na Make a Craft 3 Plu­g­in? arti­cle dis­cuss­es, one absolute­ly amaz­ing thing you can do in Php­Storm is get full auto-com­ple­tion of the entire craft and Yii2 API!

To do make the mag­ic hap­pen, install the Sym­fony plu­g­in, then Go to Pref­er­encesLan­guages & Frame­worksPHPSym­fony and check Enable Plu­g­in for this Project.

You can get this in your Twig tem­plates, too, as per the Auto-Com­plete Craft CMS 3 APIs in Twig with Php­Storm article

I would also high­ly rec­om­mend installing the Craft Code Style & Craft Inspec­tions from Pix­el & Ton­ic. Also take the time to install the Php Inspec­tions (EA Extend­ed) and Yii2 Inspec­tions plu­g­ins. These will all save you an enor­mous amount of time.

By default, any pack­ages installed in vendor/ will ini­tial­ly be exclud­ed via Pref­er­ences → Direc­to­ries and dis­close cms/vendor/nystudio107/ (or what­ev­er ven­dor direc­to­ry your plu­g­ins are in):

Exclud­ed directories

Exclud­ed fold­ers are ignored by code com­ple­tion, nav­i­ga­tion, and inspec­tions, but since these are plu­g­ins we’re work­ing on devel­op­ing, we want them included:

Don’t exclude the plu­g­ins we are developing

Also although we’re run­ning PHP 8.x in our plug­in­dev set­up, since we’re devel­op­ing for Craft CMS 3.x which requires PHP 7.0, we need to set that in Pref­er­ences → PHP so our inspec­tions are correct:

PHP Lan­guage Level

If you’re using Craft CMS 3.6 or lat­er, and know your plu­g­ins will nev­er be used on an ear­li­er ver­sion, you can set the PHP lan­guage lev­el to 7.2 if you like.

And then we’ll also want to tell it how to con­nect to our remote PHP CLI inter­preter run­ning in our Dock­er con­tain­er from Pref­er­ences → PHP click on the next to CLI inter­preter, then click on + to add a new one, and choose From Dock­er, Vagrant, VM, WSL, Remote…

Set­ting the PHP CLI interpreter

Then click on Dock­er and choose the plugindev_php_xdebug:latest image.

Link XDebug with PhpStorm

If you’ve nev­er used XDe­bug, it makes debug­ging PHP code so much eas­i­er. You can set break­points, inspect val­ues, go for­ward to back­wards through stack traces, and a whole lot more.

As men­tioned pre­vi­ous­ly, plug­in­dev uses the dual Dock­er con­tain­ers as described in the An Anno­tat­ed Dock­er Con­fig for Fron­tend Web Devel­op­ment arti­cle.

This means that all requests will go through the opti­mized PHP pro­duc­tion con­tain­er by default. But if you enable the XDEBUG_SESSION or XDEBUG_PROFILE cooke via the XDe­bug Helper brows­er exten­sion, they’ll be rout­ed to a PHP con­tain­er run­ning XDe­bug 3:

XDe­bug Helper brows­er extension

What hap­pens is XDe­bug run­ning in the plug­in­dev Dock­er con­tain­er will call out to any­thing that might be lis­ten­ing, to allow it to estab­lish an XDe­bug con­nec­tion on port 9003.

Reg­u­lar PHP and XDe­bug PHP con­tain­er requests

You can then set your break­points as you see fit, but you’ll prob­a­bly see a mes­sage like this:

Php­Storm XDe­bug no mappings

What it’s say­ing is that it does­n’t know how to trans­late between the PHP files on your local com­put­er with the PHP files in your remote Dock­er container.

So we need to set up path map­pings to tell it by click­ing on the PHP|Servers link. I start with the directory:

Php­Storm XDe­bug ven­dor mappings

The direc­to­ries on your local com­put­er are on the left, and you can click on the right to add a map­ping to where the files exists in the remote Dock­er container.

In our case, the vendor/ direc­to­ry is locat­ed at /var/www/project/vendor in the Dock­er con­tain­er, so add that, and click OK.

Now you’ll prob­a­bly see some­thing like this:

Php­Storm XDe­bug miss­ing mappings

This is good, because it means Php­Storm / XDe­bug has paused our appli­ca­tion at the break­point we set (you’ll need to click on the green ► to run through the break­point), but it does­n’t know how to map the directories.

We can fix this by adding a map­ping for each plugin:

Craft CMS Plu­g­in direc­to­ry mapping

The direc­to­ries on your local com­put­er are on the left, and you can click on the right to add a map­ping to where the files exists in the remote Dock­er container.

In our case, the craft-imageoptimize/ direc­to­ry is locat­ed at /Users/andrew/webdev/craft/craft-imageoptimize in the Dock­er con­tain­er, so add that, and click OK.

We need to tell it where these direc­to­ries for our plu­g­ins are one by one, because Php­Storm does­n’t crawl the symlinks.

Link XDebug with VScode

To use Xde­bug with VSCode install the PHP Debug exten­sion and use the fol­low­ing con­fig­u­ra­tion 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 ter­ri­bly famil­iar with using VSCode for PHP development.

Link Plugindev like a pro

You can use the plug­in­dev envi­ron­ment pro­vid­ed here with­out mod­i­fi­ca­tion to devel­op, debug, and test your plu­g­ins easily.

Or you can cus­tomize the plug­in­dev envi­ron­ment to make it your own, or just take some of the con­cepts dis­cussed here, and apply them to your own setup.

Either way, hap­py plugging!

If you want to see this plug­in­dev envi­ron­ment in action, check out this video on CraftQuest​.io!