Creating a Yii Module for Craft 3
In some cases, you may want to make new functionality available to your Craft website that does not need to be installable, like a plugin. Some examples of this include:
- Something needs to be able to hit a controller
- Something needs to be executed on every request
- Multiple plugins that depend on some of the same code
In Craft 3, it is possible to create a Yii Module to achieve this. The ability to include Yii Modules in our Craft projects adds an exciting new dimension to Craft development.
In this article, we'll take a look at building a Yii Module that can be shared between multiple Craft plugins and adds a controller action and a new template to a Craft site. This article will assume you are comfortable with (or have a general understanding of) Craft 3 plugin development, Yii Modules, and composer.
For this example we'll need:
- A Craft 3 website
- A Yii Module
- A custom plugin
Our example will take place across the following files:
- composer.json
- vendor/vendorname/mycraftplugin/composer.json
- vendor/vendorname/mycraftplugin/src/Plugin.php
- vendor/vendorname/mycraftplugin/src/templates/index.html
- vendor/vendorname/myyiimodule/src/Module.php
- vendor/vendorname/myyiimodule/src/ModuleHelper.php
- vendor/vendorname/myyiimodule/src/controllers/FancyController.php
- vendor/vendorname/myyiimodule/src/templates/index.html
Add a Yii Module in a Craft project
If your Module exists in a separate repo and does not need to be used by other plugins, you can reference it in your project's composer.json
file.
composer.json
{
...
"require": {
"vendorname/myyiimodule": "^1.0.0"
},
...
}
You can make your Module available to the Craft::$app
instance in the config/app.php
configuration file:
config/app.php
<?php
use barrelstrength\sproutcore\Module as MyYiiModule;
return [
'modules' => [
'my-yii-module' => MyYiiModule::class
],
'bootstrap' => ['my-yii-module'],
];
Your Module will now run it's Module::init()
on every page load and you can add any other functionality you need from there. You can also access your Module via the Craft::$app
instance, if you wish:
$myYiiModule = Craft::$app->getModule('my-yii-module');
This will work if your Yii Module exists in a separate repo and you are importing it as a composer dependency. If you have a project-specific module that doesn't exist as a composer dependency, you will have to take some additional steps to autoload the Module's classes in your project. We won't go into those details here.
Add a Yii Module in a Craft plugin
While some Modules may be project-specific and have no plugin dependencies, if you wish to create a Module as a complement to a plugin, you'll have to take a few more steps.
In this next example, we will use a Module to complement our plugins. In this scenario, the only thing you'll need to reference in your project's composer.json
file is your plugin:
composer.json
{
...
"require": {
"vendorname/mycraftplugin": "^1.0.0"
},
...
}
In your plugin's composer.json
file, you will require your Module:
vendor/vendorname/mycraftplugin/composer.json
{
...
"require": {
"vendorname/myyiimodule": "^1.0.0"
},
...
}
You can require your Module from one or more of your plugins. Just as all libraries required by composer, your Module will only be included once in your Craft project's vendor folder.
Note: we will extend this example at the end of this article for Modules that are being developed locally and not publicly available.
Register a Yii Module on the Craft::$app instance
In your plugin's primary class file you'll register your Yii Module and add it to the Craft::$app
instance:
vendor/vendorname/mycraftplugin/src/Plugin.php
<?php
// Make sure the ModuleHelper is loaded
use vendorname\myyiimodule\ModuleHelper;
class MyCraftPlugin extends \craft\base\Plugin
{
public function init()
{
parent::init();
// Register the Yii Module on the Craft::$app instance
SproutCoreHelper::registerMyYiiModule();
// ...
}
}
The Yii ModuleHelper class checks to see if your Module is already registered, adds it to the Craft::$app
instance, and loads it:
vendor/vendorname/myyiimodule/src/ModuleHelper.php
<?php
namespace vendorname\myyiimodule;
use Craft;
abstract class ModuleHelper
{
public static function registerMyYiiModule()
{
// Check to see if the Module is registered
if (!Craft::$app->hasModule('my-yii-module')) {
// Add it to the Craft::$app instance
Craft::$app->setModule('my-yii-module', Module::class);
// Have Craft load this module right away
// This ensures it can be used for templates
Craft::$app->getModule('my-yii-module');
}
}
}
In this example, we've chosen to register our Module with the handle my-yii-module
, however, you can choose any name you want. Be sure you choose a name that is unique as you don't want to choose something that will conflict with another plugin name.
Initialize a Yii Module
As Craft plugins have a primary Plugin.php
file, your Yii Module has a primary Module.php
file. Here is a simple version of what that file may look like:
vendor/vendorname/myyiimodule/src/Module.php
<?php
namespace vendorname\myyiimodule;
class Module extends \yii\base\Module
{
public function init()
{
parent::init();
$this->params['foo'] = 'bar';
// ...
}
}
Access a Yii Module in a Craft Plugin
Once your Module is loaded, you can access your Module using the getModule
method. Let's take another look at the plugin's primary class file and access the Module after it is loaded:
vendor/vendorname/mycraftplugin/src/Plugin.php
<?php
use vendorname\myyiimodule\ModuleHelper;
class MyCraftPlugin extends \craft\base\Plugin
{
public function init()
{
parent::init();
// Register our Yii Module on our Craft::$app instance
SproutCoreHelper::registerMyYiiModule();
// Do something with our Yii Module
$myYiiModule = Craft::$app->getModule('my-yii-module');
}
}
Adding a Controller to a Yii Module
Modules in Yii, just like Plugins in Craft, support adding custom controllers to a controllers folder. The same naming conventions apply, as Craft just gets those conventions from Yii to begin with:
vendor/vendorname/myyiimodule/src/controllers/FancyController.php
<?php
namespace vendorname\myyiimodule\controllers;
class FancyController extends BaseController
{
public function actionDoSomething()
{
// All that jazz...
}
}
You can now add a form to a template file in your Craft plugin that submits to your Yii Module's controller:
vendor/vendorname/mycraftplugin/src/templates/index.html
{% extends "_layouts/cp" %}
{% block content %}
<form method="post" accept-charset="utf-8">
{{ getCsrfInput() }}
<input type="hidden" name="action" value="my-yii-module/fancy/do-something">
<input type="text" name="whatever" value="" />
<button>Submit</button>
</form>
{% endblock %}
Notice that the URL that the action uses (admin/actions/my-yii-module/fancy/do-something
) does not include a reference to any installed plugins — just the Yii module. The my-yii-module
segment of the URL triggers your Module's controller because you registered it on the Craft::$app
instance above.
Register new Craft template paths in a Yii Module
To allow your Yii Module to display templates within Craft, you'll need to take an additional step. By default, Yii Modules do not support templates in the same way that Craft does. In our Module class file, you'll need to register your Module's template path so Craft knows it can find templates there too:
vendor/vendorname/myyiimodule/src/Module.php
<?php
namespace vendorname\myyiimodule;
// Identify any classes you need to use
use Craft;
use yii\base\Event;
use craft\web\View;
use craft\events\RegisterTemplateRootsEvent;
class Module extends \yii\base\Module
{
public function init()
{
parent::init();
// Register a new templates folder within a Module
Event::on(View::class, View::EVENT_REGISTER_CP_TEMPLATE_ROOTS, function(RegisterTemplateRootsEvent $e) {
$e->roots['my-yii-module'] = $this->getBasePath().DIRECTORY_SEPARATOR.'templates';
});
}
}
Once you've registered your Module's template folder, you can now access templates in your Module in the same way that you would access them in your plugin's template folder. The following template would be visible at the URL:
http:://website.com/admin/my-yii-module/index
vendor/vendorname/myyiimodule/src/templates/index.html
{% extends "_layouts/cp" %}
{% block content %}
My Yii Module Template!
{% endblock %}
Including a private Yii Module in a Craft Project
This article has assumed you could add your Yii Module to your project via composer. If your Module is public, the method described above will work fine. If you have not yet made your Module public, or you don't wish to, you'll have to take an additional step and tell composer where it can find your Module using the path
type in composer's repositories
array.
Here is an abbreviated composer file that illustrates how that might work:
{
...
"require": {
"vendorname/mycraftplugin": "^1.0.0"
},
"repositories": [
{
"type": "path",
"url": "../../relative/path/to/myyiimodule"
},
{
"type": "composer",
"url": "https://asset-packagist.org"
}
]
}