Andrew Welch · Insights · #PhpStorm #autocomplete #behaviors

Published , updated · 5 min read ·


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

Autocomplete for Behaviors in PhpStorm with Mixins & Veneers

Learn how to get Php­Storm Intel­liSense code com­ple­tion for Behav­iors added to your Craft CMS project using Mix­ins & Veneers

Behav­iors are a great way to extend the func­tion­al­i­ty of Craft CMS, as we’ve explored in the Extend­ing Craft CMS with Val­i­da­tion Rules and Behav­iors, Cus­tom Matrix Block Peer Val­i­da­tion Rules, and Search­ing Craft CMS Matrix Blocks articles.

How­ev­er, there is one down­side to using behav­iors: because the prop­er­ties & meth­ods are added dynam­i­cal­ly via behav­iors, we don’t get the fan­tas­tic Intel­liSense code com­ple­tion for them from PhpStorm.

This arti­cle will show you how to get auto­com­ple­tion back for your behaviors!

Link Setup

It’s assumed in this arti­cle that you’re using Php­Storm, and that you’ve installed and con­fig­ured the fol­low­ing plu­g­ins for Craft CMS development:

After installing the Sym­fony Sup­port plu­g­in, you also need to go to Set­tings Sym­fony and ensure that the Enable Plu­g­in for this Project check­box is checked.

Addi­tion­al­ly, you might want to install Craft Auto­com­plete to get full Craft API & plugin/​module API auto­com­plete in your Twig templates.

For the pur­pos­es of this arti­cle, the exam­ple code will all be from a Mod­ule we’ve added to Craft; see the Enhanc­ing a Craft CMS 3 Web­site with a Cus­tom Mod­ule arti­cle for more.

That’s it, let’s go!

Link Mixin It Up

Let’s say we want to add a sim­ple behav­ior to a Craft Com­merce Product Ele­ment to extend its functionality:

<?php
/**
 * Site module for Craft CMS
 *
 * Custom site module
 *
 * @link      https://nystudio107.com
 * @copyright Copyright (c) 2022 nystudio107
 */

namespace modules\sitemodule\behaviors;

use yii\base\Behavior;

/**
 * @author    nystudio107
 * @package   SiteModule
 * @since     1.0.0
 */
class ProductBehavior extends Behavior
{
    /**
     * Extend Product elements so they can do foo
     *
     * @return void
     */
    public function foo(): void
    {
        // Do the foo here
    }
}

This will add the foo() method to all Product Ele­ments, once we attach it in our mod­ule’s init() method:

<?php
/**
 * Site module for Craft CMS 3.x
 *
 * Custom site module
 *
 * @link      https://nystudio107.com
 * @copyright Copyright (c) 2022 nystudio107
 */

namespace modules\sitemodule;

use craft\commerce\elements\Product;
use craft\events\DefineBehaviorsEvent;
use modules\sitemodule\behaviors\ProductBehavior;
use yii\base\Event;
use yii\base\Module;

class SiteModule extends Module
{
    /**
     * @inheritdoc
     */
    public function init()
    {
        // Add the ProductBehavior behavior to Product elements
        Event::on(
            Product::class,
            Product::EVENT_DEFINE_BEHAVIORS,
            static function (DefineBehaviorsEvent $event) {
                $event->sender->attachBehaviors([
                    ProductBehavior::class,
                ]);
            }
        );
    }
}

The above code is an excerpt from the full SiteModule class, to give you con­text of how we’re attach­ing our behavior.

So great, now we have a behav­ior, but we have a problem:

We’re not get­ting auto­com­plete for this new foo() method that we’ve added via our behav­ior, which is a bum­mer. It’ll still work, but Php­Storm can’t under­stand that via sta­t­ic code analy­sis, so we get the squig­gly line under­neath it.

If the class we added the behav­ior to was our own class, we could just add a @mixin direc­tive to class’s DocBlock like this:

use craft\base\Element;
use modules\sitemodule\behaviors\ProductBehavior;

/**
 * Product model.
 *
 * @mixin ProductBehavior
 */
class Product extends Element
{
}

The @mixin direc­tive tells Php­Storm to mix all of the prop­er­ties & meth­ods from the ProductBehavior class into the Product class.

This is great if we are using behav­iors for our own class­es, but we can’t just go in and edit the Craft CMS source code as I’ve done in the exam­ple above.

Typ­i­cal­ly, we are using behav­iors to add func­tion­al­i­ty to class­es we don’t con­trol. That’s sort of the point.

So now what?

Link Veneers

To get around this, we use some­thing I call Veneers, because they are thin lit­tle lay­ers to make things shiny ✨

Veneers are just class­es that extends from the class we add our behav­ior to, and have a @mixin for our behav­ior in the DocBlock comment.

So we just need to add a class like this to our project:

<?php
/**
 * Site module for Craft CMS
 *
 * Custom site module
 *
 * @link      https://nystudio107.com
 * @copyright Copyright (c) 2022 nystudio107
 */

namespace modules\sitemodule\elements;

use craft\commerce\elements\Product;
use modules\sitemodule\behaviors\ProductBehavior;

/**
 * @author    nystudio107
 * @package   SiteModule
 * @since     1.0.0
 *
 * @mixin ProductBehavior
 */
class ProductVeneer extends Product
{
}

Notice how thin this ProductVeneer class is: it does noth­ing except extend from Product, and has the @mixin direc­tive in it we men­tioned above.

In fact, we don’t even ever instan­ti­ate this class, we just cre­ate it so Php­Storm can index it, and then use it to get the auto­com­plete we want by adding a lit­tle type hint:

        $product = new Product();
        /** @var ProductVeneer $product */
        $product->foo();

So all we need to do is add the type hint /** @var ProductVeneer $product */ in our code to tell Php­Storm Hey, actu­al­ly $product is a ProductVeneer”, and tada: 🎉

…we get the won­der­ful auto­com­plete that Php­Storm Intel­liSense code com­ple­tion provides.

Link Twig, too

We don’t want to leave Twig out of the equa­tion here either, we can get the auto­com­plete of our behav­iors in Twig, too, using a very sim­i­lar type hint:

    {# @var product \modules\sitemodule\elements\ProductVeneer #}
    {{ product.foo() }}

So now we get nice auto­com­plete of our added behav­iors in Twig, too:

Link Wrapping up

It’s quite easy to cre­ate the Veneers for any class­es you add behav­iors to.

It’s slight­ly annoy­ing to have to add the /** @var ProductVeneer $product */ type hint in every scope you need it.

But espe­cial­ly for behav­iors that add a whole lot of func­tion­al­i­ty, I think it’s infi­nite­ly worth it.

Enjoy!