Craft 3 の変更点

リッチテキストフィールド

「リッチテキスト」フィールドタイプは Craft 3 から削除され、新しい Redactor プラグインと CKEditor プラグインが追加されました。

いくつかのリッチテキストフィールドがすでに存在する場合、Redactor プラグインをインストールした時点で自動的に Redactor フィールドに変換されます。

Redactor 設定

Redactor プラグインをインストールする場合、config/redactor/ に定義された Redactor 設定が有効な JSON であることを保証する必要があります。すなわち、

  • コメントは使えません
  • (コンフィグ設定名である)すべてのオブジェクトプロパティは、ダブルクォートで囲まれていなければなりません。
  • すべての文字列は、シングルクォートではなくダブルクォートを使用しなければなりません。
// Bad:
{
  /* interesting comment */
  buttons: ['bold', 'italic']
}

// Good:
{
  "buttons": ["bold", "italic"]
}

位置選択フィールド

「位置選択」フィールドタイプは Craft 3 で削除されました。位置選択フィールドがある場合、すべてのオプションを保持したままドロップダウンフィールドに変換されます。

位置選択フィールドが必要な場合、Position Fieldtype プラグインをインストールしてください。

リモートボリューム

Amazon S3、Rackspace Cloud Files、および、Google Cloud Storage のサポートは、プラグインに移行されました。それらのサービスを Craft 2 で利用していたアセットボリュームがある場合、新しいプラグインをインストールする必要があります。

コンフィギュレーション

コンフィグ設定

いくつかの一般設定は Craft 3 でリネームされました。古い設定名は非推奨となりますが、Craft 4 までは動作し続けます。

旧設定新設定
activateAccountFailurePathinvalidUserTokenPath
backupDbOnUpdatebackupOnUpdate1
defaultFilePermissionsdefaultFileMode2
defaultFolderPermissionsdefaultDirMode
environmentVariablesaliases3
restoreDbOnUpdateFailurerestoreOnUpdateFailure
useWriteFileLockuseFileLocks
validationKeysecurityKey4

1backupOnUpdatefalse にすると、PHP によるバックアップの生成が行われないため、パフォーマンスは大きな要因になりえません。

2defaultFileMode はデフォルトで null になりました。これは、現在の環境によって決定されることを意味します。

*3 Craft 2 のコンフィグ設定 environmentVariables で定義された値をサポートする設定項目は、Craft 3 のシステム環境設定とエイリアスにセットできるようになりました。(詳細については、環境設定を参照してください。)Craft 3 にアップデートすると、サイト URL やローカルボリュームの設定は新しい @alias/sub/path 構文へ自動的に変換されます。

4securityKey は、もはやオプションではありません。まだ設定していない場合、(ファイルが存在していれば)storage/runtime/validation.key に設定します。自動生成された validation.key ファイルのバックアップは、Craft 4 で削除されるでしょう。

いくつかの設定は完全に削除されました。

ファイル設定
db.phpcollation
db.phpinitSQLs
general.phpappId
general.phpcacheMethodコンフィギュレーション > データキャッシュ設定を参照してください。)

omitScriptNameInUrlsusePathInfo

omitScriptNameInUrls 設定は、Craft 2 のデフォルトがそうであったように 'auto' にすることはもはやできません。HTTP リクエストを index.php にルーティングするようにサーバーを設定した場合、config/general.php で明示的に true にする必要があることを意味しています。

同様に、usePathInfo 設定も 'auto' にすることはできません。サーバーが PATH_INFO をサポートするよう設定されているならば、ここに true をセットできます。ただし、omitScriptNameInUrlstrue にセットできない場合のみ、必要となります。

URL ルール

config/routes.phpURL ルールを保存しているならば、Yii 2 の pattern-route 構文 にアップデートする必要があります。

  • パターンの名前付けされたパラメータは、正規表現のサブパターン((?P<ParamName>RegExp))ではなく、フォーマット(<ParamName:RegExp>)を使用して定義する必要があります。
  • 名前付けされていないパラメータ(例:([^\/]+))は、もはや許可されません。新しい名前付けされたパラメータ構文(<ParamName:RegExp>)に変換しなければなりません。
  • コントローラーアクションのルーティングは、action キーを持つ配列(['action' => 'action/path'])ではなく、文字列('action/path')として定義する必要があります。
  • テンプレートのルーティングは、文字列('template/path')ではなく、template キーを持つ配列(['template' => 'template/path'])として定義する必要があります。
// Old:
'dashboard' => ['action' => 'dashboard/index'],
'settings/fields/new' => 'settings/fields/_edit',
'settings/fields/edit/(?P<fieldId>\d+)' => 'settings/fields/_edit',
'blog/type/([^\/]+)' => 'blog/_type',

// New:
'dashboard' => 'dashboard/index',
'settings/fields/new' => ['template' => 'settings/fields/_edit'],
'settings/fields/edit/<fieldId:\d+>' => ['template' => 'settings/fields/_edit'],
'blog/type/<type:[^\/]+>' => ['template' => 'blog/_type'],

PHP 定数

いくつかの PHP 定数は Craft 3 で非推奨となり、Craft 4 で動作しなくなります

旧 PHP 定数代わりにすべきこと
CRAFT_LOCALECRAFT_SITE 定数1 を使用してください
CRAFT_SITE_URLコンフィグ設定 siteUrl、または、環境変数を使用してください

1 Craft 3 ではそれぞれのサイト / ロケールごとに独自の index.php ファイルを用意することが必須ではなくなりました。そのため、不要になったすべてのサイト / ロケールのウェブルート、および、サブフォルダーを削除することもできます。詳細については、新しい ローカライゼーションガイド を参照してください。

静的な翻訳ファイル

Craft 3 でも静的メッセージの翻訳をサポートしていますが、ディレクトリ構造が変わりました。translations/ フォルダの中にローケルごとのサブディレクトリを作成し、それぞれに翻訳カテゴリごとの PHP ファイルを作成する必要があります。

受け入れられる翻訳カテゴリは、次の通りです。

カテゴリ説明
appCraft 向けの翻訳メッセージ
yiiYii 向けの翻訳メッセージ
siteサイト固有の翻訳メッセージ
plugin-handleプラグイン向けの翻訳メッセージ

Craft 3 の translations/ フォルダの構成は、次のようになります。

translations/
└── de/
    ├── app.php
    └── site.php

ユーザーフォト

ユーザーフォトはアセットとして保存されるようになりました。Craft 3 にアップグレードすると、Craft は( <Username>/ サブフォルダを除く、Craft が事前にすべてのユーザー画像を保管している) storage/userphotos/ を「User Photos」と呼ばれる新しいアセットボリュームとして自動的に作成します。しかしながら、このフォルダはウェブルートよりも上位階層にあるため、HTTP リクエストでアクセスできません。 そのため、このボリュームをアクセスできる状態にするまで、ユーサーフォトはフロントエンドで動作しません。

次の方法で解決してください。

  1. storage/userphotos/ フォルダをウェブルート下層のどこかに移動します。(例:web/userphotos/
  2. 「設定 > アセット > ボリューム > User Photos」に移動し、新しいフォルダのロケーションに基づいてボリュームを設定します。
    • 「ファイルシステムのパス」設定を新しいフォルダのロケーションにしてアップデートします。
    • 「このボリュームのアセットにはパブリック URL が含まれます」設定を有効化します。
    • フォルダに対応する正しい「ベース URL」を設定します。
    • ボリュームを保存します。

Twig 2

Craft 3 では、テンプレート向けに独自の変更を加えた Twig 2 を使用しています。

マクロ

Twig 2 では、利用先となる各テンプレートで明示的にマクロをインポートする必要があります。親テンプレートでインクルードしている場合だけでなく、同じテンプレートファイルで定義されているときでさえも、自動的に利用することはできません。

Old:
{% macro foo %}...{% endmacro %}
{{ _self.foo() }}

New:
{% macro foo %}...{% endmacro %}
{% import _self as macros %}
{{ macros.foo() }}

未定義のブロック

Twig 1 では、存在しないブロックでさえも block() で呼び出すことができます。

{% if block('foo') is not empty %}
    {{ block('foo') }}
{% endif %}

Twig 2 では、defined のテストでない限り、エラーを返します。

{% if block('foo') is defined %}
    {{ block('foo') }}
{% endif %}

テンプレートタグ

{% paginate %} タグは {% endpaginate %} 終了タグを持たなくなったため、そのインスタンスをすべて削除します。

いくつかの Twig テンプレートタグは Craft 3 で非推奨となり、Craft 4 で完全に削除されます。

旧タグ代わりにすべきこと
{% includeCss %}{% css %} タグを使用してください
{% includeHiResCss %}{% css %} タグを使用し、自身のメディアセレクタを記述してください
{% includeJs %}{% js %} タグを使用してください
{% includeCssFile url %}{% do view.registerCssFile(url) %}
{% includeJsFile url %}{% do view.registerJsFile(url) %}
{% includeCssResource path %}アセットバンドルを使用してください
{% includeJsResource path %}アセットバンドルを使用してください

テンプレートファンクション

いくつかのテンプレートファンクションは完全に削除されました。

旧テンプレートファンクション代わりにすべきこと
craft.hasPackage()(該当なし)
craft.entryRevisions.getDraftByOffset()(該当なし)
craft.entryRevisions.getVersionByOffset()(該当なし)
craft.fields.getFieldType(type)craft.app.fields.createField(type)
craft.fields.populateFieldType()(該当なし)
craft.tasks.areTasksPending()craft.app.queue.getHasWaitingJobs()1
craft.tasks.getRunningTask()(該当なし)
craft.tasks.getTotalTasks()(該当なし)
craft.tasks.haveTasksFailed()(該当なし)
craft.tasks.isTaskRunning()craft.app.queue.getHasReservedJobs()1

1queue コンポーネントが craft\queue\QueueInterface を実装している場合のみ、使用可能です。

いくつかのテンプレートファンクションは Craft 3 で非推奨となり、Craft 4 で完全に削除されます。

旧テンプレートファンクション代わりにすべきこと
round(num)num|round
getCsrfInput()csrfInput()
getHeadHtml()head()
getFootHtml()endBody()
getTranslations()view.getTranslations()|json_encode|raw
craft.categoryGroups.getAllGroupIds()craft.app.categories.allGroupIds
craft.categoryGroups.getEditableGroupIds()craft.app.categories.editableGroupIds
craft.categoryGroups.getAllGroups()craft.app.categories.allGroups
craft.categoryGroups.getEditableGroups()craft.app.categories.editableGroups
craft.categoryGroups.getTotalGroups()craft.app.categories.totalGroups
craft.categoryGroups.getGroupById(id)craft.app.categories.getGroupById(id)
craft.categoryGroups.getGroupByHandle(handle)craft.app.categories.getGroupByHandle(handle)
craft.config.[setting](magic getter)craft.app.config.general.[setting]
craft.config.get(setting)craft.app.config.general.[setting]
craft.config.usePathInfo()craft.app.config.general.usePathInfo
craft.config.omitScriptNameInUrls()craft.app.config.general.omitScriptNameInUrls
craft.config.getResourceTrigger()craft.app.config.general.resourceTrigger
craft.locale()craft.app.language
craft.isLocalized()craft.app.isMultiSite
craft.deprecator.getTotalLogs()craft.app.deprecator.totalLogs
craft.elementIndexes.getSources()craft.app.elementIndexes.sources
craft.emailMessages.getAllMessages()craft.emailMessages.allMessages
craft.emailMessages.getMessage(key)craft.app.emailMessages.getMessage(key)
craft.entryRevisions.getDraftsByEntryId(id)craft.app.entryRevisions.getDraftsByEntryId(id)
craft.entryRevisions.getEditableDraftsByEntryId(id)craft.entryRevisions.getEditableDraftsByEntryId(id)
craft.entryRevisions.getDraftById(id)craft.app.entryRevisions.getDraftById(id)
craft.entryRevisions.getVersionsByEntryId(id)craft.app.entryRevisions.getVersionsByEntryId(id)
craft.entryRevisions.getVersionById(id)craft.app.entryRevisions.getVersionById(id)
craft.feeds.getFeedItems(url)craft.app.feeds.getFeedItems(url)
craft.fields.getAllGroups()craft.app.fields.allGroups
craft.fields.getGroupById(id)craft.app.fields.getGroupById(id)
craft.fields.getFieldById(id)craft.app.fields.getFieldById(id)
craft.fields.getFieldByHandle(handle)craft.app.fields.getFieldByHandle(handle)
craft.fields.getAllFields()craft.app.fields.allFields
craft.fields.getFieldsByGroupId(id)craft.app.fields.getFieldsByGroupId(id)
craft.fields.getLayoutById(id)craft.app.fields.getLayoutById(id)
craft.fields.getLayoutByType(type)craft.app.fields.getLayoutByType(type)
craft.fields.getAllFieldTypes()craft.app.fields.allFieldTypes
craft.globals.getAllSets()craft.app.globals.allSets
craft.globals.getEditableSets()craft.app.globals.editableSets
craft.globals.getTotalSets()craft.app.globals.totalSets
craft.globals.getTotalEditableSets()craft.app.globals.totalEditableSets
craft.globals.getSetById(id)craft.app.globals.getSetById(id)
craft.globals.getSetByHandle(handle)craft.app.globals.getSetByHandle(handle)
craft.i18n.getAllLocales()craft.app.i18n.allLocales
craft.i18n.getAppLocales()craft.app.i18n.appLocales
craft.i18n.getCurrentLocale()craft.app.locale
craft.i18n.getLocaleById(id)craft.app.i18n.getLocaleById(id)
craft.i18n.getSiteLocales()craft.app.i18n.siteLocales
craft.i18n.getSiteLocaleIds()craft.app.i18n.siteLocaleIds
craft.i18n.getPrimarySiteLocale()craft.app.i18n.primarySiteLocale
craft.i18n.getEditableLocales()craft.app.i18n.editableLocales
craft.i18n.getEditableLocaleIds()craft.app.i18n.editableLocaleIds
craft.i18n.getLocaleData()craft.app.i18n.getLocaleById(id)
craft.i18n.getDatepickerJsFormat()craft.app.locale.getDateFormat('short', 'jui')
craft.i18n.getTimepickerJsFormat()craft.app.locale.getTimeFormat('short', 'php')
craft.request.isGet()craft.app.request.isGet
craft.request.isPost()craft.app.request.isPost
craft.request.isDelete()craft.app.request.isDelete
craft.request.isPut()craft.app.request.isPut
craft.request.isAjax()craft.app.request.isAjax
craft.request.isSecure()craft.app.request.isSecureConnection
craft.request.isLivePreview()craft.app.request.isLivePreview
craft.request.getScriptName()craft.app.request.scriptFilename
craft.request.getPath()craft.app.request.pathInfo
craft.request.getUrl()url(craft.app.request.pathInfo)
craft.request.getSegments()craft.app.request.segments
craft.request.getSegment(num)craft.app.request.getSegment(num)
craft.request.getFirstSegment()craft.app.request.segments|first
craft.request.getLastSegment()craft.app.request.segments|last
craft.request.getParam(name)craft.app.request.getParam(name)
craft.request.getQuery(name)craft.app.request.getQueryParam(name)
craft.request.getPost(name)craft.app.request.getBodyParam(name)
craft.request.getCookie(name)craft.app.request.cookies.get(name)
craft.request.getServerName()craft.app.request.serverName
craft.request.getUrlFormat()craft.app.config.general.usePathInfo
craft.request.isMobileBrowser()craft.app.request.isMobileBrowser()
craft.request.getPageNum()craft.app.request.pageNum
craft.request.getHostInfo()craft.app.request.hostInfo
craft.request.getScriptUrl()craft.app.request.scriptUrl
craft.request.getPathInfo()craft.app.request.getPathInfo(true)
craft.request.getRequestUri()craft.app.request.url
craft.request.getServerPort()craft.app.request.serverPort
craft.request.getUrlReferrer()craft.app.request.referrer
craft.request.getUserAgent()craft.app.request.userAgent
craft.request.getUserHostAddress()craft.app.request.userIP
craft.request.getUserHost()craft.app.request.userHost
craft.request.getPort()craft.app.request.port
craft.request.getCsrfToken()craft.app.request.csrfToken
craft.request.getQueryString()craft.app.request.queryString
craft.request.getQueryStringWithoutPath()craft.app.request.queryStringWithoutPath
craft.request.getIpAddress()craft.app.request.userIP
craft.request.getClientOs()craft.app.request.clientOs
craft.sections.getAllSections()craft.app.sections.allSections
craft.sections.getEditableSections()craft.app.sections.editableSections
craft.sections.getTotalSections()craft.app.sections.totalSections
craft.sections.getTotalEditableSections()craft.app.sections.totalEditableSections
craft.sections.getSectionById(id)craft.app.sections.getSectionById(id)
craft.sections.getSectionByHandle(handle)craft.app.sections.getSectionByHandle(handle)
craft.systemSettings.[category](magic getter)craft.app.systemSettings.getSettings('category')
craft.userGroups.getAllGroups()craft.app.userGroups.allGroups
craft.userGroups.getGroupById(id)craft.app.userGroups.getGroupById(id)
craft.userGroups.getGroupByHandle(handle)craft.app.userGroups.getGroupByHandle(handle)
craft.userPermissions.getAllPermissions()craft.app.userPermissions.allPermissions
craft.userPermissions.getGroupPermissionsByUserId(id)craft.app.userPermissions.getGroupPermissionsByUserId(id)
craft.session.isLoggedIn()not craft.app.user.isGuest
craft.session.getUser()currentUser
craft.session.getRemainingSessionTime()craft.app.user.remainingSessionTime
craft.session.getRememberedUsername()craft.app.user.rememberedUsername
craft.session.getReturnUrl()craft.app.user.getReturnUrl()
craft.session.getFlashes()craft.app.session.getAllFlashes()
craft.session.getFlash()craft.app.session.getFlash()
craft.session.hasFlash()craft.app.session.hasFlash()

日付フォーマット

Craft によって拡張された DateTime クラスは Craft 3 で削除されました。ここに、テンプレート内で使用可能だったものと、Craft 3 で同様の働きをするもののリストを掲載します。(DateTime オブジェクトは、変数 d で表されます。実際には entry.postDatenow などになる可能性があります。)

{{ d }}(文字列として扱われる){{ d|date('Y-m-d') }}
{{ d.atom() }}{{ d|atom }}
{{ d.cookie() }}{{ d|date('l, d-M-y H:i:s T')}}
{{ d.day() }}{{ d|date('j') }}
{{ d.iso8601() }}{{ d|date('c') }}
{{ d.localeDate() }}{{ d|date('short') }}
{{ d.localeTime() }}{{ d|time('short') }}
{{ d.month() }}{{ d|date('n') }}
{{ d.mySqlDateTime() }}{{ d|date('Y-m-d H:i:s') }}
{{ d.nice() }}{{ d|datetime('short') }}
{{ d.rfc1036() }}{{ d|date('D, d M y H:i:s O') }}
{{ d.rfc1123() }}{{ d|date('r') }}
{{ d.rfc2822() }}{{ d|date('r') }}
{{ d.rfc3339() }}{{ d|date('Y-m-d\\TH:i:sP') }}
{{ d.rfc822() }}{{ d|date('D, d M y H:i:s O') }}
{{ d.rfc850() }}{{ d|date('l, d-M-y H:i:s T') }}
{{ d.rss() }}{{ d|rss }}
{{ d.uiTimestamp() }}{{ d|timestamp('short') }}
{{ d.w3c() }}{{ d|date('Y-m-d\\TH:i:sP') }}
{{ d.w3cDate() }}{{ d|date('Y-m-d') }}
{{ d.year() }}{{ d|date('Y') }}

通貨フォーマット

|currency フィルタは craft\i18n\Formatter::asCurrency() にマップされるようになりました。従来と同じ動きになりますが、引数 stripZeroCentsstripZeros にリネームされ、キーと値の両方が必要となっているため、この引数をセットしている場合はテンプレートを更新する必要があります。

Old:
{{ num|currency('USD', true) }}
{{ num|currency('USD', stripZeroCents = true) }}

New:
{{ num|currency('USD', stripZeros = true) }}

エレメントクエリ

クエリパラメータ

いくつかのエレメントクエリパラメータは削除されました。

エレメントタイプ旧パラメータ代わりにすべきこと
すべてchildOfsourceElement キーと共に relatedTo パラメータを使用してください
すべてchildFieldfield キーと共に relatedTo パラメータを使用してください
すべてparentOftargetElement キーと共に relatedTo パラメータを使用してください
すべてparentFieldfield キーと共に relatedTo パラメータを使用してください
すべてdepthlevel パラメータを使用してください
タグnametitle パラメータを使用してください
タグsetIdgroupId パラメータを使用してください
タグsetgroup パラメータを使用してください
タグorderBy:"name"orderBy パラメータに 'title' をセットしてください

いくつかのエレメントクエリパラメータは Craft 3 でリネームされました。古いパラメータは非推奨となりますが、Craft 4 までは動作し続けます。

エレメントタイプ旧パラメータ新パラメータ
すべてorderorderBy
すべてlocalesiteId または site
すべてlocaleEnabledenabledForSite
すべてrelatedTo.sourceLocalerelatedTo.sourceSite
アセットsourcevolume
アセットsourceIdvolumeId
行列ブロックownerLocalesite または siteId

limit パラメータ

limit パラメータは、100ではなく、デフォルトで null(無制限)がセットされるようになりました。

パラメータを配列にセットする

パラメータ値を配列にセットする場合、配列の大括弧を記述しなければなりません

Old:
{% set query = craft.entries()
    .relatedTo('and', 1, 2, 3) %}

New:
{% set query = craft.entries()
    .relatedTo(['and', 1, 2, 3]) %}

エレメントクエリの複製

Craft 2 では、パラメータ設定メソッド(例:.type('article'))を呼び出すときは、次のような手順になります。

  1. ElementCriteriaModel オブジェクトを複製する
  2. 複製したオブジェクトのパラメータ値を設定する
  3. 複製したオブジェクトを返す

これによって、後続のクエリに影響を与えることなく、エレメントクエリのバリエーションを実行できるようにしています。例えば、

{% set query = craft.entries.section('news') %}
{% set articleEntries = query.type('article').find() %}
{% set totalEntries = query.total() %}

この .type()type パラメータを queryclone に適用しているため、 query.total() には影響を与えません。入力タイプに関わらず、News エントリの総数を返します。

しかし、この動作は Craft 3 で変更されました。今では、パラメータ設定メソッドを呼び出すときは、次のような手順になります。

  1. 現在のエレメントクエリにパラメータ値を設定する
  2. エレメントクエリを返す

つまり、上記のサンプルコードでは type パラメータが適用されてしまうため、totalEntries には Article エントリの総数がセットされます。

Craft 2 動作に影響を与えるテンプレートがある場合、clone() ファンクションを用いて修正できます。

{% set query = craft.entries.section('news') %}
{% set articleEntries = clone(query).type('article').all() %}
{% set totalEntries = query.count() %}

クエリメソッド

findElementAtOffset() エレメントクエリメソッドは Craft 3 で削除されました。代わりに、nth() を使用してください。

いくつかのエレメントクエリメソッドは Craft 3 でリネームされました。古いメソッドは非推奨となりますが、Craft 4 までは動作し続けます。

旧メソッド新メソッド
ids(criteria)ids()(criteria パラメータは非推奨になりました)
find()all()
first()one()
last()inReverse().one()last() を見てください)
total()count()

クエリを配列として扱う

エレメントクエリを配列のように扱うサポートは Craft 3 で非推奨になり、Craft 4 で完全に削除されます。

エレメントクエリをループする必要があるときは、明示的に .all() をコールし、データベースクエリを実行して得られる結果の配列を返す必要があります。

Old:
{% for entry in craft.entries.section('news') %}...{% endfor %}
{% for asset in entry.myAssetsField %}...{% endfor %}

New:
{% for entry in craft.entries.section('news').all() %}...{% endfor %}
{% for asset in entry.myAssetsField.all() %}...{% endfor %}

エレメントクエリから結果の総数を取得したいときは、.count() メソッドを呼び出す必要があります。

Old:
{% set total = craft.entries.section('news')|length %}

New:
{% set total = craft.entries.section('news').count() %}

代替方法として、実際のクエリ結果を事前にフェッチする必要があり、かつ offsetlimit パラメータをセットしていない場合、 length フィルタを使うことで、余分なデータベースクエリを必要とせず、結果の配列の合計サイズを確認できます。

{% set entries = craft.entries()
    .section('news')
    .all() %}
{% set total = entries|length %}

last()

last() は Craft 3 で非推奨になりました。なぜなら、(query.nth(query.count() - 1) に相当する)2つのデータベースクエリを背後で実行する必要があることが明確ではないからです。

ほとんどのケースでは、.last() の呼び出しを .inReverse().one() に置き換えることで、余分なデータベースクエリを必要とせず、同じ結果を得ることができます。(inReverse() は、生成された SQL のすべての ORDER BY カラムのソート方向を反転させます。)

{# Channel entries are ordered by `postDate DESC` by default, so this will swap
   it to `postDate ASC`, returning the oldest News entry: #}

{% set oldest = craft.entries()
    .section('news')
    .inReverse()
    .one() %}

inReverse() が期待した通りに動作しないケースが2つあります。

  • SQL に ORDER BY 句が存在しない場合、反転できるものがありません
  • orderBy パラメータに yii\db\Expression オブジェクトが含まれている場合

このようなケースでは、 .last() の呼び出しを内部的な処理で置き換えることができます。

{% set query = craft.entries()
    .section('news') %}
{% set total = query.count() %}
{% set last = query.nth(total - 1) %}

エレメント

タグエレメントは、もはや name プロパティを持ちません。代わりに、title を使用してください。

すべてのエレメントの locale プロパティは非推奨となり、Craft 4 で完全に削除されます。エレメントのサイト ID が判る場合は siteId、ハンドルが判る場合は site.handle、サイトの言語が判る場合は site.language をそれぞれ使用してください。

モデル

モデルの getError('attribute') メソッドは非推奨となり、Craft 4 で完全に削除されます。代わりに、getFirstError('attribute') を使用してください。

ロケール

いくつかのロケールメソッドは Craft 3 で非推奨となり、Craft 4 で完全に削除されます。

旧メソッド代わりにすべきこと
getId()id
getName()getDisplayName(craft.app.language)
getNativeName()getDisplayName()

リクエストパラメータ

コントローラーアクションに送信するフロントエンドの <form>や JavaScript は、次の変更を加えてアップデートする必要があります。

action パラメータ

action パラメータは camelCase ではなく kebab-case に書き換えなければなりません。

Old:
<input type="hidden" name="action" value="entries/saveEntry">

New:
<input type="hidden" name="action" value="entries/save-entry">

いくつかのコントローラーアクションはリネームされました。

旧コントローラーアクション新コントローラーアクション
categories/create-categorycategories/save-category
users/validateusers/verify-email
users/save-profileusers/save-user

redirect パラメータ

redirect パラメータは、ハッシュ値に変換しなければなりません。

Old:
<input type="hidden" name="redirect" value="foo/bar">

New:
<input type="hidden" name="redirect" value="{{ 'foo/bar'|hash }}">

redirectInput() ファンクションは、ショートカットとして提供されています。

{{ redirectInput('foo/bar') }}

いくつかの redirect パラメータトークンはリネームされました。

コントローラーアクション旧トークン新トークン
entries/save-entry{entryId}{id}
entry-revisions/save-draft{entryId}{id}
entry-revisions/publish-draft{entryId}{id}
fields/save-field{fieldId}{id}
globals/save-set{setId}{id}
sections/save-section{sectionId}{id}
users/save-user{userId}{id}

CSRF トークンパラメータ

CSRF プロテクションは、Craft 3 ではデフォルトで有効になりました。(コンフィグ設定 enableCsrfProtection で)有効化していなかった場合、 コントローラーアクションで送信するフロントエンドのすべての <form> と JavaScript に新しい CSRF トークンパラメータを追加するアップデートが必要です。

{% set csrfParam = craft.app.request.csrfParam %}
{% set csrfToken = craft.app.request.csrfToken %}
<input type="hidden" name="{{ csrfParam }}" value="{{ csrfToken }}">

csrfInput() ファンクションは、ショートカットとして提供されています。

{{ csrfInput() }}

プラグイン

Craft 3 のためのプラグインアップデートを参照してください。