Craft 3 向けのプラグインアップデート

Craft 3 は CMS の完全な書き換えで、Yii 2 上で構築されています。 Yii 2 の変更のスコープのために、プロセス内のすべてのプラグインを壊すことなく、Craft を移植する実現可能な方法はありませんでした。 そのため、私たちはシステムのいくつかの主要なエリアをリファクタリングするための機会として捉えました。

リファクタリングの主たるゴールは次の通りでした。

  • 新しいコーディングガイドラインとベストプラクティスを確立し、パフォーマンス、明快さ、メンテナンス性を最適化する。
  • Craft が不必要に車輪を再発明しているエリアを識別し、それを止める。
  • モダンな開発ツールキット(Composer、PostgreSQL など)をサポートする。

最終的な結果はコアの開発とプラグイン開発で同様に、より高速で、よりスリムで、より洗練されたコードベースになりました。 あなたにも、楽しんでもらえることを望みます。

何かが欠けていると思う場合は、issue を作成してください

# ハイレベルなメモ

  • Craft は、Yii 2 で構築されました。
  • メインのアプリケーションインスタンスは、craft() ではなく、Craft::$app 経由で利用可能になりました。
  • プラグインは、プラグインについていくつかの基本的な情報を定義した composer.json ファイルを持たなければならなくなりました。
  • プラグインは、Craft や他のプラグインすべてと共有する Craft\ 名前空間ではなく、独自のルート名前空間を取得し、Craft とプラグインのコードはすべて PSR-4 仕様に従わなければならなくなりました。
  • プラグインは、Yii モジュール の拡張になりました。

# 更新履歴

Craft 3 プラグインは、releases.json ファイルではなく、CHANGELOG.md と名付けられた更新履歴を含める必要があります(更新履歴とアップデートを参照してください)。

既存の releases.json ファイルがある場合、ターミナル上で次のコマンドを使用することで、更新履歴をそれに素早く変換できます。

# go to the plugin directory
cd /path/to/my-plugin

# create a CHANGELOG.md from its releases.json
curl https://api.craftcms.com/v1/utils/releases-2-changelog --data-binary @releases.json > CHANGELOG.md

# Yii 2

Craft が構築されているフレームワーク の Yii は、2.0 向けに完全に書き直されました。 どのように変化したかについて知るには、包括的なアップグレードガイドを参照してください。

該当するセクションは、次の通りです。

# サービス名

次のコアサービス名が変更されました。

assetSources volumes
email mailer
templateCache templateCaches
templates view
userSession user

# コンポーネント

コンポーネントクラス(エレメントタイプ、フィールドタイプ、ウィジェットタイプなど)は、Craft 3 の新しいデザインパターンに従います。

Craft 2 では、それぞれのコンポーネントはモデル(例:FieldModel)とタイプ(例:PlainTextFieldType)の2つのクラスによって表されていました。 モデルはコンポーネントの主な表現であり、コンポーネントのタイプ( 例:idname、および、handle)に関わらず、常にそこにあるであろう共通のプロパティを定義しました。 その一方で、タイプは特定のコンポーネントタイプを一意(例:入力の UI)にするものを定義するための責任を負っていました。

Craft 3 では、コンポーネントタイプはモデルにとってあまり重要なクラスではなく、もはや個別の役割を果たしません。

次のように動作します。

  • getInputHtml() のような必須のコンポーネントメソッドは、インターフェースによって定義されます(例:craft\base\FieldInterface)。
  • $handle のような共通プロパティは、trait によって定義されます(例:craft\base\FieldTrait)。
  • コンポーネントタイプのベース実装は、抽象的な基本クラスによって提供されます(例:craft\base\Field)。
  • 基本クラスは、様々なコンポーネントクラスによって拡張されます(例:craft\fields\PlainText)。

# 翻訳

Craft::t() は、次の翻訳カテゴリのいずれかがセットされる $category 引数を必要とします。

  • Yii の翻訳メッセージのための yii
  • Craft の翻訳メッセージのための app
  • フロントエンドの翻訳メッセージのための site
  • プラグイン固有の翻訳メッセージのためのプラグインハンドル
\Craft::t('app', 'Entries')

フロントエンドの翻訳メッセージに加えて、site カテゴリはコントロールパネルの管理者が定義したラベルのために使用されます。

\Craft::t('app', 'Post a new {section} entry', [
    'section' => \Craft::t('site', $section->name)
])

フロントエンドの Twig コードを綺麗に保つために、 |t および |translate フィルタには特定のカテゴリを必要とせず、デフォルトで site になります。 そのため、これら2つのタグは同じ出力になります。

{{ "News"|t }}
{{ "News"|t('site') }}

# データベースクエリ

# テーブル名

Craft は、もはやデータベーステーブル接頭辞をテーブル名へ自動的に付加しないため、Yii の {{%tablename}} 構文でテーブル名を書く必要があります。

# SELECT クエリ

SELECT クエリは、craft\db\Query クラスで定義されています。

use craft\db\Query;

$results = (new Query())
    ->select(['column1', 'column2'])
    ->from(['{{%tablename}}'])
    ->where(['foo' => 'bar'])
    ->all();

# 操作クエリ

操作クエリは、Craft 2 の DbCommand クラスと同様に( Craft::$app->db->createCommand() 経由でアクセスされる)craft\db\Command のヘルパーメソッドから構築できます。

1つの顕著な違いは、ヘルパーメソッドはもはや自動的にクエリを実行しません。 そのため、execute() の呼び出しを連鎖させる必要があります。

$result = \Craft::$app->db->createCommand()
    ->insert('{{%tablename}}', $rowData)
    ->execute();

# エレメントクエリ

ElementCriteriaModel は、Craft 3 でエレメントクエリに置き換えられました。

// Old:
$criteria = craft()->elements->getCriteria(ElementType::Entry);
$criteria->section = 'news';
$entries = $criteria->find();

// New:
use craft\elements\Entry;

$entries = Entry::find()
    ->section('news')
    ->all();

# Craft コンフィグ設定

Craft のコンフィグ設定のすべては、vendor/craftcms/cms/src/config/ にあるいくつかのコンフィグクラスの実際のプロパティに移動されました。 新しいコンフィグサービス(craft\services\Config)は、それらのクラスを返すための Getter メソッド / プロパティを提供します。

// Old:
$devMode = craft()->config->get('devMode');
$tablePrefix = craft()->config->get('tablePrefix', ConfigFile::Db);

// New:
$devMode = Craft::$app->config->general->devMode;
$tablePrefix = Craft::$app->config->db->tablePrefix;

# ファイル

  • IOHelper は Yii の yii\helpers\BaseFileHelper を拡張する craft\helpers\FileHelper で置き換えられました。
  • および craft\services\Path メソッドから返されるディレクトリパスには、スラッシュが含まれなくなりました。
  • Craft のファイルシステムパスは、ハードコードされたスラッシュ(/)ではなく、(環境に依存して / または \` のどちらかがセットされる)PHP 定数のDIRECTORY_SEPARATOR` を使用します。

# Events

Craft 2 / Yii 1 のイベントハンドルを登録する伝統的な方法は、次の通りです。

$component->onEventName = $callback;

これは、コンポーネント上にイベントリスナーを直接登録します。

Craft 3 / Yii 2 では、代わりに yii\base\Component::on() を使用します。

$component->on('eventName', $callback);

Craft 2 は、サービス上にイベントハンドルを登録するために使用できる craft()->on() メソッドも提供していました。

craft()->on('elements.beforeSaveElement', $callback);

Craft 3 には直接匹敵するものがありません。 しかし、一般的に Craft 2 で craft()->on() を使用していたイベントハンドラは、Craft 3 でクラスレベルのイベントハンドラを使用する必要があります。

use craft\services\Elements;
use yii\base\Event;

Event::on(Elements::class, Elements::EVENT_BEFORE_SAVE_ELEMENT, $callback);

サービスに加えて、まだ初期化されていないコンポーネントやそれらへの参照を追跡することが簡単ではないクラスレベルのイベントハンドラを使用できます。

例えば、行列フィールドが保存されるたびに通知させたい場合、次のようにします。

use craft\events\ModelEvent;
use craft\fields\Matrix;
use yii\base\Event;

Event::on(Matrix::class, Matrix::EVENT_AFTER_SAVE, function(ModelEvent $event) {
    // ...
});

# プラグインフック

「プラグインフック」のコンセプトは Craft 3 で削除されました。 ここに以前サポートされていたフックと、Craft 3 で同じことをどのように達成できるかのリストがあります。

# 一般フック

# addRichTextLinkOptions

// Old:
public function addRichTextLinkOptions()
{
    return [
        [
            'optionTitle' => Craft::t('Link to a product'),
            'elementType' => 'Commerce_Product',
        ],
    ];
}

// New:
use craft\events\RegisterRichTextLinkOptionsEvent;
use craft\fields\RichText;
use yii\base\Event;

Event::on(RichText::class, RichText::EVENT_REGISTER_LINK_OPTIONS, function(RegisterRichTextLinkOptionsEvent $event) {
    $event->linkOptions[] = [
        'optionTitle' => \Craft::t('plugin-handle', 'Link to a product'),
        'elementType' => Product::class,
    ];
});

# addTwigExtension

次のフックのセットは、すべてのエレメントタイプで共有されている単一のイベントに結合されました。

// Old:
public function addUserAdministrationOptions(UserModel $user)
{
    if (!$user->isCurrent()) {
        return [
            [
                'label'  => Craft::t('Send Bacon'),
                'action' => 'baconater/sendBacon'
            ],
        ];
    }
}

// New:
use craft\controllers\UsersController;
use craft\events\RegisterUserActionsEvent;
use yii\base\Event;

Event::on(UsersController::class, UsersController::EVENT_REGISTER_USER_ACTIONS, function(RegisterUserActionsEvent $event) {
    if ($event->user->isCurrent) {
        $event->miscActions[] = [
            'label' => \Craft::t('plugin-handle', 'Send Bacon'),
            'action' => 'baconater/send-bacon'
        ];
    }
});
// Old:
public function getResourcePath($path)
{
    if (strpos($path, 'myplugin/') === 0) {
        return craft()->path->getStoragePath().'myplugin/'.substr($path, 9);
    }
}

:::

# addUserAdministrationOptions

// Old:
public function modifyCpNav(&$nav)
{
    if (craft()->userSession->isAdmin()) {
        $nav['foo'] = [
            'label' => Craft::t('Foo'),
            'url' => 'foo'
        ];
    }
}

// New:
use craft\events\RegisterCpNavItemsEvent;
use craft\web\twig\variables\Cp;
use yii\base\Event;

Event::on(Cp::class, Cp::EVENT_REGISTER_CP_NAV_ITEMS, function(RegisterCpNavItemsEvent $event) {
    if (\Craft::$app->user->identity->admin) {
        $event->navItems['foo'] = [
            'label' => \Craft::t('plugin-handle', 'Utils'),
            'url' => 'utils'
        ];
    }
});

# getResourcePath

// Old:
public function registerEmailMessages()
{
    return ['my_message_key'];
}

// New:
use craft\events\RegisterEmailMessagesEvent;
use craft\services\SystemMessages;
use yii\base\Event;

Event::on(SystemMessages::class, SystemMessages::EVENT_REGISTER_MESSAGES, function(RegisterEmailMessagesEvent $event) {
    $event->messages[] = [
        'key' => 'my_message_key',
        'heading' => Craft::t('plugin-handle', 'Email Heading'),
        'subject' => Craft::t('plugin-handle', 'Email Subject'),
        'body' => Craft::t('plugin-handle', 'The plain text email body...'),
    ];
});

NOTE リソースリクエストのコンセプトが Craft 3 で削除されたため、プラグインにリソースリクエストの処理を許可するこのフックには、直接 Craft 3 で匹敵するものがありません。 Craft 3 でプラグインがどのようにリソースを提供できるかを知るにはアセットバンドルを参照してください。

# modifyCpNav

テンプレートサービスは View コンポーネントに置き換えられました。

// Old:
public function registerUserPermissions()
{
    return [
        'drinkAlcohol' => ['label' => Craft::t('Drink alcohol')],
        'stayUpLate' => ['label' => Craft::t('Stay up late')],
    ];
}

// New:
use craft\events\RegisterUserPermissionsEvent;
use craft\services\UserPermissions;
use yii\base\Event;

Event::on(UserPermissions::class, UserPermissions::EVENT_REGISTER_PERMISSIONS, function(RegisterUserPermissionsEvent $event) {
    $event->permissions[\Craft::t('plugin-handle', 'Vices')] = [
        'drinkAlcohol' => ['label' => \Craft::t('plugin-handle', 'Drink alcohol')],
        'stayUpLate' => ['label' => \Craft::t('plugin-handle', 'Stay up late')],
    ];
});
// Old:
public function getCpAlerts($path, $fetch)
{
    if (craft()->config->devMode) {
        return [Craft::t('Dev Mode is enabled!')];
    }
}

// New:
use craft\events\RegisterCpAlertsEvent;
use craft\helpers\Cp;
use yii\base\Event;

Event::on(Cp::class, Cp::EVENT_REGISTER_ALERTS, function(RegisterCpAlertsEvent $event) {
    if (\Craft::$app->config->general->devMode) {
        $event->alerts[] = \Craft::t('plugin-handle', 'Dev Mode is enabled!');
    }
});

:::

# registerCachePaths

フロンドエンドリクエストでプラグインが提供するテンプレートをレンダリングしたい場合、View コンポーネントを CP のテンプレートモードに設定する必要があります。

// Old:
public function modifyAssetFilename($filename)
{
    return 'KittensRule-'.$filename;
}

// New:
use craft\events\SetElementTableAttributeHtmlEvent;
use craft\helpers\Assets;
use yii\base\Event;

Event::on(Assets::class, Assets::EVENT_SET_FILENAME, function(SetElementTableAttributeHtmlEvent $event) {
    $event->filename = 'KittensRule-'.$event->filename;

    // Prevent other event listeners from getting invoked
    $event->handled = true;
});
// Old:
public function registerCpRoutes()
{
    return [
        'cocktails/new' => 'cocktails/_edit',
        'cocktails/(?P<widgetId>\d+)' => ['action' => 'cocktails/editCocktail'],
    ];
}

// New:
use craft\events\RegisterUrlRulesEvent;
use craft\web\UrlManager;
use yii\base\Event;

Event::on(UrlManager::class, UrlManager::EVENT_REGISTER_CP_URL_RULES, function(RegisterUrlRulesEvent $event) {
    $event->rules['cocktails/new'] = ['template' => 'cocktails/_edit'];
    $event->rules['cocktails/<widgetId:\d+>'] = 'cocktails/edit-cocktail';
});

:::

# registerEmailMessages

// Old:
public function registerSiteRoutes()
{
    return [
        'cocktails/new' => 'cocktails/_edit',
        'cocktails/(?P<widgetId>\d+)' => ['action' => 'cocktails/editCocktail'],
    ];
}

// New:
use craft\events\RegisterUrlRulesEvent;
use craft\web\UrlManager;
use yii\base\Event;

Event::on(UrlManager::class, UrlManager::EVENT_REGISTER_SITE_URL_RULES, function(RegisterUrlRulesEvent $event) {
    $event->rules['cocktails/new'] = ['template' => 'cocktails/_edit'];
    $event->rules['cocktails/<widgetId:\d+>'] = 'cocktails/edit-cocktail';
});

Rather than defining the full message heading/subject/body right within the Craft::t() call, you can pass placeholder strings (e.g. 'email_heading') and define the actual string in your plugin’s translation file.

# registerUserPermissions

次のコントロールパネルテンプレートフックはリネームされました。

// Old:
public function addEntryActions($source)
{
    return [new MyElementAction()];
}

// New:
use craft\elements\Entry;
use craft\events\RegisterElementActionsEvent;
use yii\base\Event;

Event::on(Entry::class, Element::EVENT_REGISTER_ACTIONS, function(RegisterElementActionsEvent $event) {
    $event->actions[] = new MyElementAction();
});
// Old:
public function modifyEntrySortableAttributes(&$attributes)
{
    $attributes['id'] = Craft::t('ID');
}

// New:
use craft\base\Element;
use craft\elements\Entry;
use craft\events\RegisterElementSortOptionsEvent;
use yii\base\Event;

Event::on(Entry::class, Element::EVENT_REGISTER_SORT_OPTIONS, function(RegisterElementSortOptionsEvent $event) {
    $event->sortOptions['id'] = \Craft::t('app', 'ID');
});

:::

# getCpAlerts

ページのどこかに任意の HTML を含めたい場合、View コンポーネントで beginBody または endBody イベントを使用してください。

// Old:
public function modifyEntrySources(&$sources, $context)
{
    if ($context == 'index') {
        $sources[] = [
            'heading' => Craft::t('Statuses'),
        ];

        $statuses = craft()->elements->getElementType(ElementType::Entry)
            ->getStatuses();
        foreach ($statuses as $status => $label) {
            $sources['status:'.$status] = [
                'label' => $label,
                'criteria' => ['status' => $status]
            ];
        }
    }
}

// New:
use craft\base\Element;
use craft\elements\Entry;
use craft\events\RegisterElementSourcesEvent;
use yii\base\Event;

Event::on(Entry::class, Element::EVENT_REGISTER_SOURCES, function(RegisterElementSourcesEvent $event) {
    if ($event->context === 'index') {
        $event->sources[] = [
            'heading' => \Craft::t('plugin-handle', 'Statuses'),
        ];

        foreach (Entry::statuses() as $status => $label) {
            $event->sources[] = [
                'key' => 'status:'.$status,
                'label' => $label,
                'criteria' => ['status' => $status]
            ];
        }
    }
});
// Old:
public function defineAdditionalEntryTableAttributes()
{
    return [
        'foo' => Craft::t('Foo'),
        'bar' => Craft::t('Bar'),
    ];
}

// New:
use craft\elements\Entry;
use craft\events\RegisterElementTableAttributesEvent;
use yii\base\Event;

Event::on(Entry::class, Element::EVENT_REGISTER_TABLE_ATTRIBUTES, function(RegisterElementTableAttributesEvent $event) {
    $event->tableAttributes['foo'] = ['label' => \Craft::t('plugin-handle', 'Foo')];
    $event->tableAttributes['bar'] = ['label' => \Craft::t('plugin-handle', 'Bar')];
});

:::

# modifyAssetFilename

プラグインがカスタムタスクタイプを提供する場合、それらをジョブに変換する必要があります。

// Old:
public function getEntryTableAttributeHtml(EntryModel $entry, $attribute)
{
    if ($attribute === 'price') {
        return '$'.$entry->price;
    }
}

// New:
use craft\base\Element;
use craft\elements\Entry;
use craft\events\SetElementTableAttributeHtmlEvent;
use yii\base\Event;

Event::on(Entry::class, Element::EVENT_SET_TABLE_ATTRIBUTE_HTML, function(SetElementTableAttributeHtmlEvent $event) {
    if ($event->attribute === 'price') {
        /** @var Entry $entry */
        $entry = $event->sender;

        $event->html = '$'.$entry->price;

        // Prevent other event listeners from getting invoked
        $event->handled = true;
    }
});
// Old:
public function getTableAttributesForSource($elementType, $sourceKey)
{
    if ($sourceKey == 'foo') {
        return craft()->elementIndexes->getTableAttributes($elementType, 'bar');
    }
}

:::

# ルーティングフック

# registerCpRoutes

use craft\web\twig\variables\CraftVariable;
use yii\base\Event;

Event::on(CraftVariable::class, CraftVariable::EVENT_INIT, function(Event $event) {
    /** @var CraftVariable $variable */
    $variable = $event->sender;
    $variable->set('componentName', YourVariableClass::class);
});

# registerSiteRoutes

// Old:
$this->renderTemplate('pluginHandle/path/to/template', $variables);

// New:
return $this->renderTemplate('plugin-handle/path/to/template', $variables);

# getElementRoute

<old-handle><oldhandle> を以前のプラグインハンドル(kebab-caseonewordalllowercase)に、<new-handle> を新しいプラグインハンドルに置き換えてください。

{# Old: #}
{% set extraPageHeaderHtml %}
    <a href="{{ url('recipes/new') }}" class="btn submit">{{ 'New recipe'|t('app') }}</a>
{% endset %}

{# New: #}
{% block actionButton %}
    <a href="{{ url('recipes/new') }}" class="btn submit">{{ 'New recipe'|t('app') }}</a>
{% endblock %}
{# Old: #}
{% block main %}
    <div class="grid first" data-max-cols="3">
        <div class="item" data-position="left" data-colspan="2">
            <div id="recipe-fields" class="pane">
                <!-- Primary Content -->
            </div>
        </div>
        <div class="item" data-position="right">
            <div class="pane meta">
                <!-- Secondary Content -->
            </div>
        </div>
    </div>
{% endblock %}

{# New: #}
{% block content %}
    <div id="recipe-fields">
        <!-- Primary Content -->
    </div>
{% endblock %}

{% block details %}
    <div class="meta">
        <!-- Secondary Content -->
    </div>
{% endblock %}

:::

# エレメントフック

プラグインがカスタムエレメントタイプ、フォールドタイプ、または、ウィジェットタイプを提供する場合、新しいクラス名とマッチする適切なテーブルの type カラムをアップデートする必要があります。

プラグインが Craft 2 の locales テーブルにカスタム外部キーを作成していた場合、Craft 3 のアップグレードでは、locales テーブルがもはや存在しないため、代わりにsites テーブルの外部キーを付けた新しいカラムが自動的に追加されます。

# addEntryActions, addCategoryActions, addAssetActions, & addUserActions

データは問題なく動作するはずですが、古いカラムを削除し、Craft によって新しく作成されたものをリネームすることを望むでしょう。

public function addEntryActions($source)
{
    return [new MyElementAction()];
}
use craft\elements\Entry;
use craft\events\RegisterElementActionsEvent;
use yii\base\Event;

Event::on(Entry::class, Element::EVENT_REGISTER_ACTIONS, function(RegisterElementActionsEvent $event) {
    $event->actions[] = new MyElementAction();
});

:::

# modifyEntrySortableAttributes, modifyCategorySortableAttributes, modifyAssetSortableAttributes, & modifyUserSortableAttributes

// Old:
craft()->tasks->createTask('MyTask', 'Custom description', array(
    'mySetting' => 'value',
));

// New:
Craft::$app->queue->push(new MyJob([
    'description' => 'Custom description',
    'mySetting' => 'value',
]));

# modifyEntrySources, modifyCategorySources, modifyAssetSources, & modifyUserSources

public function modifyEntrySources(&$sources, $context)
{
    if ($context == 'index') {
        $sources[] = [
            'heading' => Craft::t('Statuses'),
        ];

        $statuses = craft()->elements->getElementType(ElementType::Entry)
            ->getStatuses();
        foreach ($statuses as $status => $label) {
            $sources['status:'.$status] = [
                'label' => $label,
                'criteria' => ['status' => $status]
            ];
        }
    }
}

# defineAdditionalEntryTableAttributes, defineAdditionalCategoryTableAttributes, defineAdditionalAssetTableAttributes, & defineAdditionalUserTableAttributes

public function defineAdditionalEntryTableAttributes()
{
    return [
        'foo' => Craft::t('Foo'),
        'bar' => Craft::t('Bar'),
    ];
}

# getEntryTableAttributeHtml, getCategoryTableAttributeHtml, getAssetTableAttributeHtml, & getUserTableAttributeHtml

public function getEntryTableAttributeHtml(EntryModel $entry, $attribute)
{
    if ($attribute === 'price') {
        return '$'.$entry->price;
    }
}

# getTableAttributesForSource

// Craft 2:
public function getTableAttributesForSource($elementType, $sourceKey)
{
    if ($sourceKey == 'foo') {
        return craft()->elementIndexes->getTableAttributes($elementType, 'bar');
    }
}

NOTE エレメントインデックスがレンダリングされる前に、プラグインがエレメントタイプのテーブル属性を完全に変更することを許可するこのフックには、直接 Craft 3 で匹敵するものがありません。 Craft 3 で最も近いのは、管理者がエレメントインデックスのソースをカスタマイズする際に、エレメントタイプの利用可能なテーブル属性を変更するために使用できる craft\base\Element::EVENT_REGISTER_TABLE_ATTRIBUTES イベントです。

# テンプレート変数

テンプレート変数は、もはや Craft 3 のものではありません。 しかしながら、プラグインは init イベントをリスニングすることで、グローバルな craft 変数にカスタムサービスを登録することができます。

// Craft 3:
use craft\web\twig\variables\CraftVariable;
use yii\base\Event;

Event::on(CraftVariable::class, CraftVariable::EVENT_INIT, function(Event $event) {
    /** @var CraftVariable $variable */
    $variable = $event->sender;
    $variable->set('componentName', YourVariableClass::class);
});

componentName をあなたが望む craft オブジェクトの変数名に置き換えてください。 後方互換性のために、古い camelCased プラグインハンドルにすることをお勧めします。 )

# テンプレートのレンダリング

The TemplatesService has been replaced with a View component.

craft()->templates->render('pluginHandle/path/to/template', $variables);

# コントローラーアクションのテンプレート

コントローラーの renderTemplate() メソッドは、あまり変更されていません。 唯一の違いは、テンプレートの出力やリクエストの最後に使用されていたのに対して、現在ではコントローラーアクションが返すべきレンダリングされたテンプレートを返します。

$this->renderTemplate('pluginHandle/path/to/template', $variables);

# フロントエンドリクエストのプラグインテンプレートのレンダリング

If you want to render a plugin-supplied template on a front-end request, you need to set the View component to the CP’s template mode:

$oldPath = craft()->templates->getTemplatesPath();
$newPath = craft()->path->getPluginsPath().'pluginhandle/templates/';
craft()->templates->setTemplatesPath($newPath);
$html = craft()->templates->render('path/to/template');
craft()->templates->setTemplatesPath($oldPath);

# コントロールパネルのテンプレート

If your plugin has any templates that extend Craft’s _layouts/cp.html control panel layout template, there are a few things that might need to be updated.

# extraPageHeaderHtml

extraPageHeaderHtml 変数のサポートは削除されました。 ページヘッダーのプライマリアクションボタンを作成するには、新しい actionButton を使用してください。

{% set extraPageHeaderHtml %}
    <a href="{{ url('recipes/new') }}" class="btn submit">{{ 'New recipe'|t('app') }}</a>
{% endset %}

# ページ全体のグリッド

If you had a template that overrode the main block, and defined a full-page grid inside it, you should divide the grid items’ contents into the new content and details blocks.

さらに、すでに持っていたいくつかの <div class="pane"> は、 通常 pane クラスを失っています。

{% block main %}
    <div class="grid first" data-max-cols="3">
        <div class="item" data-position="left" data-colspan="2">
            <div id="recipe-fields" class="pane"><!-- Primary Content --></div>
        </div>
        <div class="item" data-position="right">
            <div class="pane meta"><!-- Secondary Content --></div>
        </div>
    </div>
{% endblock %}

# コントロールパネルテンプレートフック

The following control panel template hooks have been renamed:

cp.categories.edit.right-pane cp.categories.edit.details
cp.entries.edit.right-pane cp.entries.edit.details
cp.users.edit.right-pane cp.users.edit.details

# リソースリクエスト

Craft 3 にはリソースリクエストのコンセプトがありません。 フロントエンドリソースの働きについての情報は、アセットバンドル を参照してください。

# 任意の HTML の登録

If you need to include arbitrary HTML somewhere on the page, use the beginBody or endBody events on the View component:

craft()->templates->includeFootHtml($html);

# バックグラウンドタスク

Craft’s Tasks service has been replaced with a job queue, powered by the Yii 2 Queue Extension.

If your plugin provides any custom task types, they will need to be converted to jobs:

// Old:
class MyTask extends BaseTask
{
    public function getDescription()
    {
        return 'Default description';
    }

    public function getTotalSteps()
    {
        return 5;
    }

    public function runStep($step)
    {
        // do something...
        return true;
    }
}

Adding jobs to the queue is a little different as well:

craft()->tasks->createTask('MyTask', 'Custom description', array(
    'mySetting' => 'value',
));

# アップグレードマイグレーションの記述

Craft 2 インストール向けにプラグインにマイグレーションパスを与える必要があるかもしれません。

Craft がプラグインをアップデートなのか、新規インストールなのか判断させることを最初に決定する必要があります。 プラグインハンドルが(UpperCamelCase から kebab-case になる他に)変更されない場合、Craft は新しいバージョンのアップデートとみなします。 しかし、ハンドルがより重要な形で変わっているなら、Craft はそれを認識せず、完全に新しいプラグインとして判断します。

ハンドルが(一般的に)同じ名前で止まる場合、“craft3_upgrade” のように名付けられた新しいマイグレーションを作成してください。 アップグレードコードは、他のマイグレーション同様に safeUp() メソッドに入れます。

ハンドルが変更されている場合、代わりにインストールマイグレーションにアップグレードコードを配置する必要があります。 これを出発点として使用してください。

<?php
namespace ns\prefix\migrations;

use craft\db\Migration;

class Install extends Migration
{
    public function safeUp()
    {
        if ($this->_upgradeFromCraft2()) {
            return;
        }

        // Fresh install code goes here...
    }

    private function _upgradeFromCraft2(): bool
    {
        // Fetch the old plugin row, if it was installed
        $row = (new \craft\db\Query())
            ->select(['id', 'handle', 'settings'])
            ->from(['{{%plugins}}'])
            ->where(['in', 'handle', ['<old-handle>', '<oldhandle>']])
            ->one();

        if (!$row) {
            return false;
        }

        // Update this one's settings to old values
        $projectConfig = \Craft::$app->projectConfig;
        $oldKey = "plugins.{$row['handle']}";
        $newKey = 'plugins.<new-handle>';
        $projectConfig->set($newKey, $projectConfig->get($oldKey));

        // Delete the old plugin row and project config data
        $this->delete('{{%plugins}}', ['id' => $row['id']]);
        $projectConfig->remove($oldKey);

        // Any additional upgrade code goes here...

        return true;
    }

    public function safeDown()
    {
        // ...
    }
}

Replace <old-handle> and <oldhandle> with your plugin’s previous handle (in kebab-case and onewordalllowercase), and <new-handle> with your new plugin handle.

追加のアップグレード処理を加える必要がある場合、_upgradeFromCraft2() メソッドの最後(return 文の前)に配置してください。 (プラグインの新規インストール向けの)通常のインストールマイグレーションコードは、safeUp() の最後に入れる必要があります。

# コンポーネントクラス名

If your plugin provides any custom element types, field types, or widget types, you will need to update the type column in the appropriate tables to match their new class names.

# エレメント

$this->update('{{%elements}}', [
    'type' => MyElement::class
], ['type' => 'OldPlugin_ElementType']);

# フィールド

$this->update('{{%fields}}', [
    'type' => MyField::class
], ['type' => 'OldPlugin_FieldType']);

# ウィジェット

$this->update('{{%widgets}}', [
    'type' => MyWidget::class
], ['type' => 'OldPlugin_WidgetType']);

# ロケールの外部キー

If your plugin created any custom foreign keys to the locales table in Craft 2, the Craft 3 upgrade will have automatically added new columns alongside them, with foreign keys to the sites table instead, as the locales table is no longer with us.

The data should be good to go, but you will probably want to drop the old column, and rename the new one Craft created for you.

// Drop the old locale FK column
$this->dropColumn('{{%mytablename}}', 'oldName');

// Rename the new siteId FK column
MigrationHelper::renameColumn('{{%mytablename}}', 'oldName__siteId', 'newName', $this);