Craft 3.1 Dev Preview Notes

Craft 3.1 Dev Preview is available to give plugin developers a chance to add support for Soft Deletes and Project Config to their plugins, before the official 3.1 release. It SHOULD NOT be used for actual project work of any kind!

About Craft 3.1 Dev Preview #

You can read a high level overview of the new features in Craft 3.1 in the announcement.

How to Install 3.1 Dev Preview #

To install Craft 3.1 Dev Preview, follow these steps:

  1. Install Craft 3 normally, or use an existing Craft 3 installation.
  2. Update your craftcms/cms constraint in composer.json to 3.1.x-dev as 3.1.0-alpha.1:

    "require": {
       "craftcms/cms": "3.1.x-dev as 3.1.0-alpha.1",
       "...": "..."
    }
  3. Run composer update.
  4. Once Composer is finished, access your Control Panel and click the “Finish up” button.

Soft Deletes #

Support for Element Types #

All element types are soft-deletable without any extra effort. However, element types must opt-into being restorable, if desired.

To make an element type restorable, give it the Restore element action:

use craft\elements\actions\Restore;

// ... 

protected static function defineActions(string $source = null): array
{
    $action = [];

    // ...

    $actions[] = \Craft::$app->elements->createAction([
        'type' => Restore::class,
        'successMessage' => Craft::t('<plugin-handle>', '<Type> restored.'),
        'partialSuccessMessage' => Craft::t('<plugin-handle>', 'Some <type> restored.'),
        'failMessage' => Craft::t('<plugin-handle>', '<Type> not restored.'),
    ]);

    return $actions;
}

With that in place, users will be able to type is:trashed into the search bar on your element index page to see trashed elements, and restore the ones they didn’t mean to delete.

Support for Other Things #

If your plugin introduces a concept to Craft that isn’t an element type, that too can be soft-deletable by following these steps:

  1. Create a migration that adds a new dateDeleted column to the database table.

    public function safeUp()
    {
        $this->addColumn('{{%tablename}}', 'dateDeleted', $this->dateTime()->null()->after('dateUpdated'));
        $this->createIndex(null, '{{%tablename}}', ['tablename'], false);
    }
  2. If you have an Active Record class for this table, add the SoftDeleteTrait to it.

    use craft\db\ActiveRecord;
    use craft\db\SoftDeleteTrait;
    
    // ...
    
    class MyRecord extends ActiveRecord
    {
        use SoftDeleteTrait;
    
        // ...
    }
  3. Update any code that currently calls your record’s delete() method, and have it call softDelete() instead.
  4. Update any code that deletes rows via Craft::$app->db->createCommand()->delete(), and have it call softDelete() instead.

Project Config #

How to Enable project.yaml #

Using project.yaml to store your Project Config could have unexpected consequences if you’re not prepared for it, so that feature is disabled by default.

Before you enable it, make a database backup from your production environment, and restore it on all other environments. That way you can be sure that the UIDs of your sections, fields, etc., are all consistent across environments. (If you don’t have a production environment yet, pick whichever environment has the most recent administrative changes.)

Once all environments are synced up with the same database backup, open up config/general.php and enable the useProjectConfigFile config setting.

return [
    'useProjectConfigFile' => true,
    // ...
];

When to Add Support for Project Config #

Project Config support should only be added to things that only Admin users can edit, which could be considered part of the project’s configuration. For example, sections and custom fields are stored in the project config, but individual entries are not.

How to Add Support for Project Config #

To add Project Config support to a plugin, the service’s CRUD methods will need to be shifted around a bit.

  • CRUD methods should update the Project Config, rather than changing things in the database directly.
  • Database changes should only happen as a result of changes to the Project Config.

Here’s a high level look at what a save action might look like:

┌──────────────────────────────────────────┐
│                                          │
│      Sections::saveSection($recipe)      │
│                                          │
└──────────────────────────────────────────┘
                      │                     
                      ▼                     
┌──────────────────────────────────────────┐
│                                          │
│  ProjectConfig::set('<path>', $config)   │
│                                          │
└──────────────────────────────────────────┘
                      │                     
                      ▼                     
┌──────────────────────────────────────────┐
│                                          │
│  Sections::handleChangedSection($event)  │
│                                          │
└──────────────────────────────────────────┘
                      │                     
                      ▼                     
┌──────────────────────────────────────────┐
│                                          │
│    Command::insert('<table>', '$data)    │
│                                          │
└──────────────────────────────────────────┘

Relevant ProjectConfig Service functions:

The craft\services\ProjectConfig class provides the API for working with the Project Config. You can access it from your plugin via Craft::$app->projectConfig.

  • To add or update an item in the Project Config, use set().
  • To Remove an item from the Project Config, use remove().
  • To be notified when something is added to the Project Config, use onAdd().
  • To be notified when something is updated in the Project Config, use onUpdate().
  • To be notified when something is removed from the Project Config, use onRemove().

These methods work the same regardless of whether the useProjectConfigFile config setting is enabled.