A central principle of the Sprout Plugin Suite is to create an experience – for both users and developers – that looks and feels like the native experience with Craft CMS. Toward that end, wherever possible, Sprout adopts conventions set forth by Craft CMS. General conventions include:
|Craft CMS codebase||Use Craft's APIs in the same way that Craft uses them|
|Coding Guidelines||Follow conventions outlined in Craft's Coding Guidelines|
|Craft Code Style||Craft Code Style settings for PhpStorm|
|Craft Inspections||Craft Inspections for PhpStorm|
|Php Inspections (EA Extended)||More code conventions for PhpStorm|
|Yii Inspections||Yii 2 and Craft-specific inspections in PhpStorm|
|Sass Mixins for Craft CMS||Use Craft's Control Panel style conventions|
|Craft UI||Vue.js components and styles for Craft CMS apps|
Where there are no clear Craft conventions for our codebase or workflows we endeavor to establish our own conventions that align with the Craft User Experience and are consistent with cultural conventions in the Craft community. An incomplete list of these conventions are outlined below.
We use a git workflow in the spirit of the Git branching model with a few updates to better fit our workflow.
As we have to maintain multiple master copies of our plugins, instead of a
master branch, we maintain multiple master version branches. These branches are named using the format
v2 would represent all releases for the
v2.x branch of the software. These version numbers correlate with the plugin version numbers, not the Craft version number, as we may have multiple major releases of a plugin for a single release of Craft.
# Example plugin branches - develop - feature/feature-name - bugfix/bugfix-name - v1 - v2 - v3
The name of a plugin will be used in several different contexts. We use the following conventions:
When possible, we follow conventions in Craft's folder architecture in our plugins.
Root directory and key src files
├── .github ├── .gitignore ├── CHANGELOG.md ├── composer.json ├── lib ├── LICENSE.md ├── README.md └── src ├── Plugin.php └── translations └── en
README.md and any other general information files should be kept to a minimum and point users toward our docs, where we maintain more comprehensive documentation. As we maintain several plugins, it gets tedious to update references in numerous general information files and our documentation serves as a centralized place for this type of information about our plugins.
Similarly, we aim to keep the
composer.json file as simple as possible. Don't add
hasCpSettings to this file. They should go in the primary plugin module class to more easily toggle the settings without running into issues with cached values in Craft's
We rename the primary module class
Plugin.php to use the name of the plugin (i.e.
SproutForms.php). This update requires that we set the
composer.json extra->class setting to define the Plugin.php as a file with the name of the plugin itself.
Third-party libraries are added to the
Resources and templating
└── src └── web └── assets └── [assetbundle] ├── dist └── [CustomAssetBundle].php └── twig └── variables └── [CustomVariable.php]
All asset bundles are managed in the
src/web/assets folder and all things Twig (variables, filters, nodes, etc.) are managed in the
Components and Integrations
└── src └── base └── [BaseCustomComponentType].php └── [customcomponenttype] ├── [CustomComponentType1].php └── [CustomComponentType2].php └── fields ├── [CustomField1].php └── [CustomField2].php └── integrations └── [pluginname] └── [integrationcustomcomponenttype] ├── [IntegrationCustomComponentType1].php └── [IntegrationCustomComponentType2].php └── templates └── _components └── [customcomponenttype] ├── [customcomponenttype1] └── [customcomponenttype2] └── fields ├── [customfield1] └── [customfield2] └── _integrations └── [pluginname] └── [integrationcustomcomponenttype] ├── [integrationcustomcomponenttype1] └── [integrationcustomcomponenttype2]
We manage various types of components within our plugins. The structure above illustrates how we organize three classes of components. We use the example of a Custom Field as how we would organize extending Craft component within our plugins.
|Craft Components|| ||Craft component classes get placed in a folder named after the component type in the |
|Custom Components|| ||Custom Component classes get placed in a folder named after the component type in the |
|Integration Components|| ||Integration Component classes get placed in a folder named after the component type in the |
Github community templates
└── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── ISSUE_TEMPLATE bug-report.md feature-request.md ├── SECURITY.md └── SUPPORT.md
Community templates should link to our pages in our documentation with more comprehensive information wherever possible.
Many Sprout plugins share functionality and this code is managed in shared Yii Modules.
|barrelstrength/sprout-base||Common settings and UI components|
|barrelstrength/sprout-base-email||Common email functionality|
|barrelstrength/sprout-base-reports||Common reporting functionality|
|barrelstrength/sprout-base-import||Common import functionality|
|barrelstrength/sprout-base-fields||Common field functionality|
|barrelstrength/sprout-base-lists||Common list functionality|
|barrelstrength/sprout-base-redirects||Common sitemap functionality|
|barrelstrength/sprout-base-sitemaps||Common redirects functionality|
|barrelstrength/sprout-base-uris||Common URL-enabled Section functionality|
Use the default Craft conventions for translations. This allows us to benefit from the Yii Inspections that allow us to bulk add and remove translations.
Each plugin or module maintains it's own translation file. As some plugins depend on multiple modules for their functionality, this may mean that someone translating a plugin will also have to translate translation files in other modules. For example, to completely translate Sprout Forms one would need to translate the files in Sprout Forms, Sprout Base Fields, and Sprout Base.
Exceptions are for developers, not for users. Exception messages should not be translated:
BAD: throw new Exception(Craft::t('sprout-forms, 'Something happened')); GOOD: throw new Exception('Something happened');
Due to our application structure using shared modules, in some cases migrations may need to be run by multiple plugins and we cannot know which order they will get run in. To address this, we use the following conventions:
- Make sure every migration can be run twice, without throwing errors if it has already been run once.
- All migrations that affect a plugin with a shared module should be placed in the base module and instances of those migrations should be created in each respective plugin where they are needed.
Migration naming will use the date in the first segment and the second segment will just represent the order that they should be run in for a particular release. The following migrations are all be part of a release on the same day, and are ordered 1, 2, 3 in the order they should run:
m190101_000001_migration_description.php m190101_000002_migration_description.php m190101_000003_migration_description.php
Migrations used by multiple plugins
Any migration instance that is just running a migration in base module should use the same name as the base migration and append the plugin name that it is being run from.
m190101_000001_migration_description.php // Sprout Base Email m190101_000001_migration_description_sproutemail.php // Sprout Email m190101_000001_migration_description_sproutforms // Sprout Forms
Testing Prior to Release
To test one or more plugins and modules under development on real websites before releases, changes can be pushed to a development branch and pulled into any appropriate project for testing.
In this example, we grab the latest on the
develop branch for the Sprout SEO plugin and the Sprout Base Redirects module. To ensure composer things it's working with the release numbers we're using in our
composer.json we can tell composer what version number to use for the code we are testing:
composer require barrelstrength/sprout-seo:"dev-develop as 4.2.0" barrelstrength/sprout-base-redirects:"dev-develop as 1.1.1"
dev-develop should pull in the latest commit on the
develop branch. For more specific tests, you can also target a specific commit hash:
composer require barrelstrength/sprout-seo:"dev-develop as 4.2.0" barrelstrength/sprout-base-redirects:"dev-develop#dfae1a922cdb5dd32fd8a813839fddc26ff412b0 as 1.1.1"
For additional troubleshooting, consider some of the following steps:
rm composer.lock # Remove the composer.lock file composer clear-cache # Clear composers cache composer remove ... # Try removing the package you are testing before installing it rm -r ./vendor # Remove the entire ./vendor directory and rebuild it with `composer update`