Andrew Welch
Published , updated · 5 min read · RSS Feed
Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.
Autocomplete for Behaviors in PhpStorm with Mixins & Veneers
Learn how to get PhpStorm IntelliSense code completion for Behaviors added to your Craft CMS project using Mixins & Veneers
Behaviors are a great way to extend the functionality of Craft CMS, as we’ve explored in the Extending Craft CMS with Validation Rules and Behaviors, Custom Matrix Block Peer Validation Rules, and Searching Craft CMS Matrix Blocks articles.
However, there is one downside to using behaviors: because the properties & methods are added dynamically via behaviors, we don’t get the fantastic IntelliSense code completion for them from PhpStorm.
This article will show you how to get autocompletion back for your behaviors!
Link Setup
It’s assumed in this article that you’re using PhpStorm, and that you’ve installed and configured the following plugins for Craft CMS development:
- Yii2 Inspections — Adds inspections for Yii2 & Craft CMS
- Symfony Support — Adds support for Symfony & Twig inspections
After installing the Symfony Support plugin, you also need to go to Settings → Symfony and ensure that the Enable Plugin for this Project checkbox is checked.
Additionally, you might want to install Craft Autocomplete to get full Craft API & plugin/module API autocomplete in your Twig templates.
For the purposes of this article, the example code will all be from a Module we’ve added to Craft; see the Enhancing a Craft CMS 3 Website with a Custom Module article for more.
That’s it, let’s go!
Link Mixin It Up
Let’s say we want to add a simple behavior to a Craft Commerce Product Element 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 Elements, once we attach it in our module’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 context of how we’re attaching our behavior.
So great, now we have a behavior, but we have a problem:
We’re not getting autocomplete for this new foo() method that we’ve added via our behavior, which is a bummer. It’ll still work, but PhpStorm can’t understand that via static code analysis, so we get the squiggly line underneath it.
If the class we added the behavior to was our own class, we could just add a @mixin directive 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 directive tells PhpStorm to mix all of the properties & methods from the ProductBehavior class into the Product class.
This is great if we are using behaviors for our own classes, but we can’t just go in and edit the Craft CMS source code as I’ve done in the example above.
Typically, we are using behaviors to add functionality to classes we don’t control. That’s sort of the point.
So now what?
Link Veneers
To get around this, we use something I call Veneers, because they are thin little layers to make things shiny ✨
Veneers are just classes that extends from the class we add our behavior to, and have a @mixin for our behavior 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 nothing except extend from Product, and has the @mixin directive in it we mentioned above.
In fact, we don’t even ever instantiate this class, we just create it so PhpStorm can index it, and then use it to get the autocomplete we want by adding a little 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 PhpStorm “Hey, actually $product is a ProductVeneer”, and tada: 🎉
…we get the wonderful autocomplete that PhpStorm IntelliSense code completion provides.
Link Twig, too
We don’t want to leave Twig out of the equation here either, we can get the autocomplete of our behaviors in Twig, too, using a very similar type hint:
{# @var product \modules\sitemodule\elements\ProductVeneer #}
{{ product.foo() }}
So now we get nice autocomplete of our added behaviors in Twig, too:
Link Wrapping up
It’s quite easy to create the Veneers for any classes you add behaviors to.
It’s slightly annoying to have to add the /** @var ProductVeneer $product */ type hint in every scope you need it.
But especially for behaviors that add a whole lot of functionality, I think it’s infinitely worth it.
Enjoy!