Andrew Welch · Insights · #PhpStorm #craft-3 #plugin

Published , updated · 5 min read ·


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

So You Wanna Make a Craft 3 Plugin?

Whether you’re port­ing an exist­ing plu­g­in from Craft CMS 2.x or writ­ing your first plu­g­in for Craft 3, this arti­cle will show you how to do it

This arti­cle will show you how to write a plu­g­in for Craft CMS 3.x. I’m not going to show you how to write a spe­cif­ic plu­g­in, but rather focus on the process and method­olo­gies that you’ll use.

Check out the A Craft CMS Plu­g­in Local Devel­op­ment Envi­ron­ment arti­cle for details on how to set up a local devel­op­ment envi­ron­ment for plu­g­in development.

As the Craft 3 Beta Exec­u­tive Sum­ma­ry arti­cle notes, now is the per­fect time to start work­ing on a Craft 3 plu­g­in if you’d like it to be ready by the time Craft 3 comes out of beta, and is released.

The time to start working on Craft 3 plugins is now

Even if you’re a fron­tend devel­op­er who has nev­er ven­tured into back­end devel­op­ment”, I’m here to tell you that there’s no rea­son you can’t do it.

Programming is just programming. The languages and frameworks will come and go; the underlying skills you develop are what matter.

Let’s say you go on vaca­tion to a for­eign coun­try, and want to rent a car to dri­ve around. You would­n’t say to your­self I can’t dri­ve here, every­thing is total­ly different!”

No. Because you still know how to dri­ve. The street names and local cus­toms you can learn as you go, the fact that you know how to dri­ve is what matters.

So it is with devel­op­ment. Don’t lim­it your­self by say­ing I don’t do XXX” because you absolute­ly can do it. There will be a decent bit to learn, but then again, there always will be.

Knowing how to learn is probably the single most important skill in being a developer, because the tech du jour changes constantly.

Alright, so we’ve had our lit­tle pep talk, and we’re ready to go! But what exact­ly is a plu­g­in, and why would I want to write one?

Link What is a plugin?

A plu­g­in is just code that extends an exist­ing sys­tem. Devel­op­ers can’t antic­i­pate every sin­gle sce­nario, so they cre­ate an archi­tec­ture for extend­ing the func­tion­al­i­ty of their product.

Some­times they are called plu­g­ins”, some­times they are called mod­ules”. What­ev­er you call them, and what­ev­er lan­guage they are writ­ten in, they exist to give you a way to extend an exist­ing sys­tem. In our case, the plu­g­ins we’re going to make for Craft 3 are writ­ten in PHP.

Fun fact: PHP originally stood for “Personal Home Page”

At it’s core, your plu­g­in is just code you write, but it needs to be wrapped up in the scaf­fold­ing that defines the API cre­at­ed by the devel­op­ers of the sys­tem that you’re extending.

This is prob­a­bly the most intim­i­dat­ing part of writ­ing a plu­g­in, because you’re essen­tial­ly hav­ing to learn what­ev­er bou­tique API you’re giv­en. Some APIs are good, some are not… but they are all your entre into adding the func­tion­al­i­ty you want.

This is probably the least interesting part of developing a plugin, as the scaffolding is just boilerplate.

For­tu­nate­ly, these days there are scaf­fold­ing tools for just about every­thing you might want to build, which takes the drudgery out of doing it. They cre­ate the skele­ton scaf­fold­ing, you drop your own code into it. It lets you hit the ground running.

For Craft CMS, I cre­at­ed a web­site called plug​in​fac​to​ry​.io that will cre­ate all of this Craft CMS plu­g­in scaf­fold­ing for you, for either the 2.5.x or 3.0.x APIs. The web­site is pow­ered by a Yeo­man gen­er­a­tor called gen­er­a­tor-craft­plu­g­in that you can install local­ly to use from the com­mand line as well, if that’s your thing.

But first, let’s tool up.

Link Tooling makes everything easier

You know those cheesy scenes in action movies where the hero gears up with a ridicu­lous amount of weapon­ry before going to wage some ridicu­lous­ly impos­si­ble battle?

Well, we’re going to do the same thing before we start work­ing on any PHP development.

First, you’re going to need some kind of local devel­op­ment envi­ron­ment. I pre­fer using a real VM as described in the Local Devel­op­ment with Vagrant / Home­stead arti­cle, but I know some very smart peo­ple who use oth­er solu­tions like Valet or MAMP.

I like using Home­stead because it comes with all the tools I’m going to use pre-installed, like Com­pos­er for installing PHP pack­ages (which you will need for Craft 3), Black­fire for per­for­mance test­ing, XDe­bug for debug­ging, as well as an Node, and an Nginx serv­er envi­ron­ment that repli­cates pro­duc­tion closely.

How­ev­er, use what­ev­er works for you.

Link PhpStorm

One oth­er bit of tool­ing that I urge you to join me in using for PHP devel­op­ment is Php­Storm.

PhpStorm is not free; but it is infinitely worth it if you do any amount of PHP development.

Remem­ber, there is a dif­fer­ence between the price of some­thing, and the cost. In this case, if you do any amount of PHP devel­op­ment, the price of Php­Storm is tiny com­pared to the cost of not using it. It’s that good.

If you don’t believe me, spend a few min­utes watch­ing the (free) Be Awe­some in Php­Storm Lara­Casts. There are a lot of use­ful set­up videos ear­ly on, but make sure you watch videos 0612 where you will real­ly learn how Php­Storm saves you the most pre­cious resource you have: time.

Php­Storm auto-completion

One absolute­ly amaz­ing thing you can do in Php­Storm is get full auto-com­ple­tion of the entire craft. APIs in your Twig tem­plates, too! 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.

Then all you need to do is put this inspec­tion hint at the top of your Twig templates:

{# @var craft \craft\web\twig\variables\CraftVariable #}

…and just like that, you get the same mag­ic auto-com­ple­tion of in your Twig tem­plates that you have in your PHP code. And since the entire Craft appli­ca­tion is avail­able in our Twig tem­plates now, this makes writ­ing Twig code that uses the Craft app APIs so much nicer.

For an even sweet­er way to do it, check out the Auto-Com­plete Craft CMS 3 APIs in Twig with Php­Storm article!

And that does­n’t even get into the abil­i­ty of Php­Storm to take advan­tage of Yii2 inspec­tions, set break­points, and so on. Remem­ber, there are some real­ly good edi­tors out there in Sub­lime, VS Code, and Atom… but Php­Storm is an Inte­grat­ed Devel­op­ment Envi­ron­ment (IDE), not just an editor.

I used to think that these peo­ple who were writ­ing beau­ti­ful PHP code were just PHP-gods, but once I start­ed using Php­Storm, I real­ized they just had real­ly awe­some tooling.

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.

Link Getting a Host Craft 3 Install Setup

We’re not quite ready to start div­ing in an devel­op­ing a plu­g­in yet. First, we need to set up a Craft 3 install as a host” where we will devel­op and debug our plugin.

We do this because we need a Craft 3 envi­ron­ment in which our plu­g­in will run in order to be able to devel­op and debug it with any rea­son­able efficacy.

Just fol­low the Instal­la­tion Instruc­tions from the Craft 3 Doc­u­men­ta­tion to get a new Craft install set up. As of this writ­ing, Craft 3 is at beta 18, so the spe­cif­ic instruc­tions may have changed some­what, but it’ll be some­thing like this:

composer create-project craftcms/craft /home/vagrant/sites/craft3 -s beta

Then go grab a cup of cof­fee; it will use Com­pos­er to cre­ate a new Craft 3 project for you, and down­load all of the depen­den­cies, which can take a while.

At this point you’ll need to set up your .env file as per the Instal­la­tion Instruc­tions, or use Craft3 Mul­ti-Envi­ron­ment to set up your local dev environment.

Then you’ll need to fin­ish the Instal­la­tion Instruc­tions to set up your data­base, web serv­er, etc. which is all very spe­cif­ic to what­ev­er local devel­op­ment envi­ron­ment you’re using, so we won’t get into that here.

Check out the A Craft CMS Plu­g­in Local Devel­op­ment Envi­ron­ment arti­cle for details on how to set up a local devel­op­ment envi­ron­ment for plu­g­in development.

If you’re using Home­stead, this just involves edit­ing your Homestead.yaml file to add the new site:

sites:
    - map: craft3.dev
      to: /home/vagrant/sites/craft3/web

databases:
    - craft3

And then do homestead reload --provision and away you go.

If you are using Home­stead, and decide to use Php­Storm (please, do it!) this would be a very good time to check out the Using Php­Storm with Vagrant / Home­stead arti­cle for how to get that set up as smooth as butter.

Link Can I write some code yet?

Phew, that was a lot of set­up and we haven’t even writ­ten a sin­gle line of code. Well, wel­come to mod­ern devel­op­ment. If you’re doing it for the first time, set­ting up your tool­ing is going to take a bit of time, and there are a lot of pieces to get work­ing together.

How­ev­er, it will make things eas­i­er in the end, and remem­ber, you only need to do this set­up once.

So yeah, let’s write some code.

Whether you are writ­ing a new plu­g­in or port­ing a Craft 2.x plu­g­in to Craft 3, the work­flow that I sug­gest you use is this:

  1. Gen­er­ate your plug­in’s scaf­fold­ing at plug​in​fac​to​ry​.io
  2. Fill in the skele­ton scaf­fold­ing with the code that makes your plu­g­in unique
  3. If you find you need an addi­tion­al com­po­nent, add it ad-hoc by using a local­ly installed gen­er­a­tor-craft­plu­g­in

Make sure you select Craft CMS Version 3.0.x for the API Ver­sion. So here’s what this might look like:

Using plug​in​fac​to​ry​.io to gen­er­ate a Craft 3 plu­g­in scaffolding

We’re just cre­at­ing a plu­g­in with a sin­gle Con­troller com­po­nent. Click on Build My Plu­g­in and it will down­load a .zip file with your plu­g­in scaf­fold­ing cre­at­ed for you. Decom­press it, and move it to some­where that you keep your devel­op­ment work (this must be some­where that is acces­si­ble by your VM as well, just like your websites).

Since Craft now relies heav­i­ly on Com­pos­er, we need to add our new plu­g­in as a com­pos­er depen­den­cy. We do this by edit­ing the composer.json file that was cre­at­ed when we had Com­pos­er cre­ate a Craft 3 project for us. Mine looks like this right now:

{
  "name": "craftcms/craft",
  "description": "Craft CMS",
  "keywords": [
    "craft",
    "cms",
    "craftcms",
    "project"
  ],
  "license": "MIT",
  "homepage": "https://craftcms.com/",
  "type": "project",
  "support": {
    "email": "support@craftcms.com",
    "issues": "https://github.com/craftcms/cms/issues",
    "forum": "https://craftcms.stackexchange.com/",
    "source": "https://github.com/craftcms/cms",
    "docs": "https://craftcms.com/docs",
    "rss": "https://craftcms.com/changelog.rss"
  },
  "minimum-stability": "beta",
  "require": {
    "php": ">=7.0.0",
    "craftcms/cms": "^3.0.0-beta.10",
    "craftcms/plugin-installer": "^1.0.0",
    "vlucas/phpdotenv": "^2.4.0"
  },
  "scripts": {
    "post-root-package-install": [
      "php -r \"file_exists('.env') || copy('.env.example', '.env');\""
    ]
  },
  "repositories": [
    {
      "type": "composer",
      "url": "https://asset-packagist.org"
    }
  ]
}

First, we need to tell Com­pos­er that we require our plu­g­in as part of the Craft 3 project by adding it in here:

"require": {
    "php": ">=7.0.0",
    "craftcms/cms": "^3.0.0-beta.10",
    "craftcms/plugin-installer": "^1.0.0",
    "vlucas/phpdotenv": "^2.4.0",
    "roave/security-advisories": "dev-master",
    "nystudio107/contact": "^1.0.0"
  },

Nor­mal­ly we’d now do a composer update which would cause it to look for the pack­age nystudio107/contact and install it as part of our project. This is how we’d do it for any pack­ages that are pub­licly avail­able via Pack­ag­ist, but we’re devel­op­ing this plu­g­in locally.

So instead, we can tell Com­pos­er to look in a local path for repos­i­to­ries as well:

"repositories": [
    {
      "type": "path",
      "url": "../../webdev/craft/contact/"
    }
]

The path ../../webdev/craft/contact/ is just where my plu­g­in lives on my local file sys­tem, rel­a­tive to the cur­rent Craft project. If you’ll be work­ing on a num­ber of plu­g­ins, you can use a wild­card * to tell it to look at all sub-direc­to­ries in our fold­er, so we can put all of our devel­op­ment plu­g­ins there:

"repositories": [
    {
        "type": "path",
        "url": "../../webdev/craft/*",
        "options": {
            "symlink": true
        }
    }
]

Okay great, now we can just do composer update and it’ll work its mag­ic to cre­ate the autoloader for our new­ly added plugin:

vagrant@homestead:~/sites/craft3$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing nystudio107/contact (1.0.0): Symlinking from ../../webdev/craft/contact
Writing lock file
Generating autoload files

You may also want to add the fol­low­ing to your composer.json file:

"config": {
    "optimize-autoloader": true
  },

This will cause Com­pos­er to opti­mize the autoload class maps after any com­pos­er install or com­pos­er update. It’s some­thing that The P&T folks said they are going to add to the default composer create-project craftcms/craft project, but until it’s they do, it can’t hurt to do it manually.

vagrant@homestead:~/sites/craft3$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating optimized autoload files

Final­ly we can do some cod­ing! A sym­link to our plu­g­in is now in vendor/nystudio107/contact and we can just work on this plu­g­in using what­ev­er edi­tor or IDE you pre­fer, using our host Craft 3 web­site to test & debug it:

The Con­tact Craft 3 plu­g­in as a Com­pos­er dependency

If you real­ize lat­er on that you real­ly need anoth­er Con­troller, we can lever­age gen­er­a­tor-craft­plu­g­in to add com­po­nents ad-hoc. You’ll need to have Node installed, and then:

  1. Install Yeo­man glob­al­ly via sudo npm install -g yo
  2. Install the generator-craftplugin glob­al­ly via sudo npm -g install generator-craftplugin

Then from inside of your plug­in’s root direc­to­ry (the one that con­tains the .craftplugin file) you can do things like: yo craftplugin --controllerName="woofer" to cre­ate a new Con­troller named woofer” or yo craftplugin --consolecommandName="woofer" --pluginComponents="consolecommands" to add an entire­ly new Con­sole Com­mand plu­g­in com­po­nent named woofer”.

There’s more infor­ma­tion on how this all works, as well as a video show­ing it off in the Adding to an exist­ing plu­g­in sec­tion of the generator-craftplugin documentation.

Link Hey, wait a minute, you didn’t write any code!

I told you I was­n’t going to show you how to write a spe­cif­ic plu­g­in, but rather show you the process and method­olo­gies that’ll make your life eas­i­er. Using Php­Storm will be a mon­strous win when you’re try­ing to learn the Craft 3 plu­g­in API, because of things like this: 

Php­Storm code inspections

Beyond that, I look to 4 places when try­ing to learn how to write a spe­cif­ic Craft plugin:

  1. The offi­cial Craft 3 Plu­g­in Devel­op­ment documentation
  2. Exist­ing Craft 3 Plu­g­ins to see how oth­er peo­ple did it
  3. By look­ing at the Craft 3 source code itself (it’s actu­al­ly very well documented)
  4. The Yii2 Doc­u­men­ta­tion, since much of the Craft 3 API is now a thin lay­er on top of Yii2

And when all else fails, often ask­ing a ques­tion in #craft3 or #plugindev on the Craft CMS Slack will get you some good answers.

Also extreme­ly use­ful for under­stand­ing Composer/​Packagist semver nam­ing is the Pack­ag­ist Semver Check­er.

If you’re mov­ing from Craft 2.x to Craft 3.x plu­g­in devel­op­ment, you’ll find that a lot of the Craft-spe­cif­ic plu­g­in lay­er has been removed, and the APIs and method­olo­gies lean heav­i­ly on Yii2. Which is a great thing, since you’re learn­ing a skill that has broad­er impli­ca­tions, and since there’s good doc­u­men­ta­tion available.

It may seem over­whelm­ing at first, but spend­ing a bit of time learn­ing about how Yii2 thinks about the world is very well-spent, because then you real­ize you can start lever­ag­ing all of the nic­i­ties that Yii2 offers… and you end up writ­ing less code.

You’ll also find that most of the meth­ods you’re used to still exist, they’ve just been moved around a bit. It’s sort of like some­one has come in and re-arranged the fur­ni­ture in your house.

While this may be irk­some at first, the refac­tor­ing of the Craft 3 APIs is a huge win long-term. Every­thing makes more log­i­cal sense, and con­forms to many Yii2-isms.

Good luck, and hap­py coding!