Fatal error
当サイトが利用しているXserverビジネススタンダードの新サーバーへの移行を行いました。MariaDBが10.5.xから10.11.xになりましたので、Drupal10.6.8からDrupal11.3.xにアップデートしました。
詳しくは、前回記事にしていますので、興味がございましたらご覧ください。
WordPress
共用サーバーですので、当サイト以外にWordPressで構築した3サイトも運営しています。そのうち、1サイトのコンタクトフォームでFatar Errorが出ていたので、原因を特定し対処しました。
このエラーの原因を考えると、DrupalとWordPressの開発思想の違いが見えて面白いなと感じたので、記事にまとめました。
今回Fatal errorが出ていたのはWordPressで構築した姉妹サイトの
https://portfolio.hooked-on01.com(Opens in a new tab/window)
になります。
具体的には、コンタクトフォームを開くと、以下のエラーが表示されています。
Fatar Error
Fatal error: Cannot redeclare ImaginemBlocks\Widgets\mtheme_select_contact_form() (previously declared in /home/xb123456/hooked-on01.com/public_html/portfolio.hooked-on01.com/wp-content/plugins/imaginem-blocks-ii/widgets/contactform.php:102) in /home/xb123456/hooked-on01.com/public_html/portfolio.hooked-on01.com/wp-content/plugins/imaginem-blocks-ii/widgets/contactform.php on line 102このエラーは、WordPressで良く見かける「関数の重複定義(二重定義)」による Fatal Error(致命的エラー)です。
エラーの意味
imaginem-blocks-ii というプラグインの contactform.php というファイルの102行目で、mtheme_select_contact_form() という関数を定義しようとしたが、「すでに同じ名前の関数が宣言されているため、再度定義することはできません」という理由で処理が強制終了しています。
同じファイル(しかも同じ102行目)が重複先として指定されているため、「このファイル(または関数)が何らかの理由で2回読み込まれてしまっている」状態です。
原因の検証
コンタクトフォームのウィジェットに関わるエラーなので、関わるプラグインとテーマを洗い出します。
- BlackSilver : Elementorを使ったテーマ
- Elementor : ページビルダーのプラグイン
- Contact Form 7 : コンタクトフォーム用のプラグイン
- eCAPTCHA : フォームメール送信用認証セキュリティ
ここに
- Akismet : WordPress用スパムメール対策
を追加しています。
コードの確認
contactform.phpの102行目を確認して見ます。
if ( function_exists( 'wpcf7' ) ) {
function mtheme_select_contact_form(){ //102行目
$wpcf7_form_list = get_posts(array(
'post_type' => 'wpcf7_contact_form',
'showposts' => 999,
));
$posts = array();
if ( ! empty( $wpcf7_form_list ) && ! is_wp_error( $wpcf7_form_list ) ){
foreach ( $wpcf7_form_list as $post ) {
$options[ $post->ID ] = $post->post_title;
}
return $options;
}
}
$this->add_control(
'mtheme_wpcf7_form',
[
'label' => esc_html__( 'Select your contact form 7', 'imaginem-blocks-ii'),
'label_block' => true,
'type' => Controls_Manager::SELECT,
'options' => mtheme_select_contact_form(),
]
);
}ディレクトリ構成を見てみるとimaginem-blocks-ii というプラグインはElementorのようです。contactform.phpはElementorでContact7を読み込み編集するための記述のようです。
テーマのBlack SilverがElementorを使用しているので、テーマが用意するコンタクトフォームに関わるスクリプトのようです。
現状のコードは `if ( function_exists( 'wpcf7' ) )` となっており、これは「Contact Form 7(wpcf7)のプラグインが存在するかどうか」のチェックになっています。
そのため、「Contact Form 7 が有効化されている環境」でこのファイルが2回読み込まれると、結局この中の関数(`mtheme_select_contact_form`)を2回定義しようとしてしまい、エラーが発生してしまいます。
エラーを回避するには、この条件分岐を「`mtheme_select_contact_form` という関数がまだ定義されていないか」というチェック(否定の `!` をつけた `function_exists`)に変更する必要があります。
コードを修正します。
// 条件を変更:wpcf7 が存在し、かつ mtheme_select_contact_form が「まだ定義されていない」場合
if ( function_exists( 'wpcf7' ) && ! function_exists( 'mtheme_select_contact_form' ) ) {
function mtheme_select_contact_form(){
$wpcf7_form_list = get_posts(array(
'post_type' => 'wpcf7_contact_form',
'showposts' => 999,
));
$options = array(); // ※バグ防止のため $posts から $options に修正して初期化
if ( ! empty( $wpcf7_form_list ) && ! is_wp_error( $wpcf7_form_list ) ){
foreach ( $wpcf7_form_list as $post ) {
$options[ $post->ID ] = $post->post_title;
}
return $options;
}
}
} // ← ここまで- 安全のために「Contact Form 7のチェック」と「関数の重複チェック」の両方を兼ね備えた形にしています。
- `if` 文の条件に `&& ! function_exists( 'mtheme_select_contact_form' )` を追加しました。これで2回目に読み込まれたときはスルーされるようになります。
- 元のコードの5行目で `$posts = array();` と定義されているのに、その後 `$options` にデータを入れようとしていたため、PHPの警告(Warning)が出ないよう `$options = array();` に修正。
エラーは無事消えました。
考察
今回のエラーは、プロセスのバッティングが原因で起きています。エラーが発生した時に不思議だったのが、エラー画面をリロードするとエラーが消えます。
同じ関数が、他に存在していることで起きているエラーなので、重複している関数を探す必要がありますが、思い当たる節がありません。
症状がスクリプトの記述の問題であれば何回リロードしても100%エラーになります。そのことから、スクリプトではなくプラグインとそれぞれのオブジェクトキャッシュのプロセスやタイミングの問題ではないかと考えます。
修正は、関数の重複チェックと重複時に警告を出さないように修正することでエラーを消しています。
複数のプラグインを使用
現在の使用状況は
- BlackSilver : Elementorを使ったテーマ
- Elementor : ページビルダーのプラグイン
- Contact Form 7 : コンタクトフォーム用のプラグイン
- reCAPTCHA : フォームメール送信用認証セキュリティ
ここに
- Akismet : WordPress用スパムメール対策
となっています。
プラグイン同士のプロセスで、関数がバッティングした?と考えられますが、現実的にはありえないと考えます。
キャッシュプロセス
もう一つ考えられるのが、キャッシュのプロセスと、実際のプロセスがバッティングしたのではと考えます。
サイトはXserverの高速キャッシュである「Xアクセラレータ」やWordPressのプラグインで「WP-Optimizeのキャッシュ機能」を有効にしています。1回目のアクセス時にキャッシュを生成しようとして処理が重複し、2回目は生成されたキャッシュを読み込むためエラーを回避できているのではと考えます。
おそらくこの事が原因ではないかと考えています。
対応
実際に、どこのプロセスでエラーを発生させたのかまでは特定出来ていませんが、関数がバッティングしてしまうプロセスを回避することで問題なく動いています。
他に考えられる事として、サーバー移転により、データベースの処理速度が上がった事があります。またWordPress7.0へのアップデートによる処理の最適化なども原因としては考えられます。
処理速度の向上により、これまでプロセス間の処理の遅延により成り立っていた時間軸で順序化されたプロセスが、処理能力が向上したことで遅延がなくなり同時処理されてしまった事が、(処理の不具合ではない擬似的とも言えます)エラーを発生させたということも考えられます。
WordPressとDrupal
今回、WordPressで発生したエラーの対処を行い、WordPressの開発思想の意味が少し見えています。
DrupalとWordPressが異なるのは、WordPressのプラグインやテーマは多少の不備があっても動きますが、同様のケースはDrupalでは動きません。ここで開発思想の違いが見えてきます。
その違いについて、まとめてみます。
WordPress
- 思想 : 民主化→誰でも簡単に参加できるプロジェクト
- ベース技術 : 独自の手続き型関数 + 一部オブジェクト指向
- エラー : 多少の不具合は無視してもとにかく動かす。
WordPressはブログから始まった、誰でも触れるシステムを目指したため、多少古いコード(非推奨コード)や型が違っていても、PHP側でなんとか解釈して動かそうとします。
WordPressの多くは、今回エラーになった `mtheme_select_contact_form()` のような「グローバル関数」で動いています。グローバル関数はどこからでも簡単に呼び出せるので開発が手軽な反面、今回のような名前空間の衝突を持ちやすくなります。
WordPressのプラグインが「多少の不備があっても動く」のは、アクションフック・フィルターフックという仕組みのおかげです。これは、WordPress本体の動きに対して、外からプラグインが「割り込み(横槍)」を入れる仕組みです。
WordPressのフック:「割り込む順番」が曖昧でも、WordPress側が適当に処理してくれます。しかし、今回のように環境(サーバーの処理速度やAkismetのフックなど)が変わると、急に割り込み順序が狂ってバッティングを起こします。
Drupal
- 思想 : 野心的なデジタル体験→堅牢性と拡張性、エンタープライズ品質
- ベース技術 : Symfony(エンタープライズ向けPHPフレームワーク)
- エラー : 設計図と異なる処理は動作させない
Drupalは、政府・大企業の基幹システムなどの大規模で高負荷に耐えうる設計思想で開発されています。土台にあるSymfonyは、オブジェクトの設計図(インターフェース)やデータの型を厳密に定義するため、少しでも設計図と違うコードがあると、安全のためにシステム全体を動かさず保護します。
Drupal(Symfony)では、関数を裸で定義することはほぼありません。すべて「クラス(Class)」や「サービス(Service)」という箱の中に閉じ込められ、名前空間(Namespace)で厳密に住所が区別されています。
そのため、同じ名前の処理があっても絶対にバッティングしない仕組みになっていますが、その代わり「ルール通りに正しく依存関係を定義(依存性の注入:DIなど)」して記述しないと、1行も動いてくれません。
Drupalのイベントリスナー : Symfonyベースのイベント駆動型になっており、どの処理がいつ、どの優先順位で実行されるかが「サービスコンテナ」という中央集権的な仕組みで完璧に管理されています。ここで管理外の動きをしようとすると、即座にエラーとして弾かれます。
開発思想の違い
民主化
漠然としたイメージを言語化してみると、イメージしていた事の理由が見えてきます。
WordPressの民主的な思想が、アルゴリズムにおいて誰でも自由にアクセス可能であることや、ルールをあまり厳しくしないことで、開発の敷居が低くなっています。
これは、実運用においても、技術的な知識をあまり持たずともWebサイトを構築して運営できるWordPressの特徴とも一致しています。
このことは、WorPress公式Webサイトでも述べられています。
https://ja.wordpress.org/about/(Opens in a new tab/window)
今回のエラーは、ある意味、WordPressだから発生したエラーとも言えます。このことは決して悪いことではなく、Drupalで似たような事例があった場合インストールも出来ず、プログラムが動きません。
多少の不備があっても、とりあえず動くシステムは、初学者や、技術的な知識を持たない利用者にとっての恩恵は大きく、そのことが、WordPressの普及の原動力の一因でもあると考えると納得がいきます。
このWordPressの普及こそが設計思想である民主化を具現化しているとも言えます。
野心的なデジタル体験
DrupalはWordPressの思想である民主化とは根本的な思想が異なります。
Drupalの開発思想に創設者のDries Buytaert氏が挙げる
- "For ambitious digital experiences"(野心的なデジタル体験のために)
と長年Drupalコミュニティで語られている
- "Come for the software, stay for the community"(ソフトウェアを目的に集まり、コミュニティの魅力によって定着する)
があります。
構造化されたシステムや高度なセキュリティ、拡張性の高さはこの「野心的なデジタル体験」のためであると考えると納得がいきます。
高度なシステムを安定的に動かすという思想が、今回WordPressのエラーで感じた、プログラム上の不備もなんとか許容する姿勢と異なり、Drupalではプログラム上不備があったら最初から拒絶し、システムの安定性を守るという結果につながっています。
Drupalコミュニティが、良好な状況を維持する理由に、互助の精神が強いことがあります。これは「ソフトウェアを目的に集まり、コミュニティの魅力によって定着する」の具現化であり、優れた技術者が、コミュニティの魅力に触れ、互助の精神で、高度な開発を行う事につながっています。
当ブログ開設当初に感じ記事にしていた、Drupalの初学の敷居の高さと、理解後の使い勝手の良さ、拡張に対する可能性の高さ、構造から紐解くと理解しやすいシステムの意味が、今回言語化したことで、鮮明になります。
オープンソース
WordPressとDrupalの特徴と、同じエラー要素に対しての動作が根本的に異なることから、設計思想の違いを考えてみました。
何をベースにこのような設計思想が、生まれたかを考えると、両者に共通しているのは、オープンソースの根本である互助の精神やボランティア精神が、システムのポリシーに異なる形で影響を与えています。
原理原則は、オープンソースの互助の精神をどう実現するかという共通した理念と、オープンソースが実現する無限の可能性にリスペクトや希望を持ってプロジェクトが日々進捗しています。
両者とも、CMSとしてWeb上のプログラムをオープンソースで開発しています。出来上がったCMSはその成り立ちから、利用用途が棲み分けられて、世界中で利用されています。
コアプログラムに設計思想がしっかり反映されています。
- WordPress : コアプログラムはシンプルで、フックにより参加者がテーマやプラグインを自由に持ち込み発展させる仕組み。
- Drupal : コアプログラムにある程度の機能を持たせ、管理は開発陣が徹底して行い、不確定要素を排除する。テーマやモジュールの更新状況により、推奨、非推奨の判断も開発陣が判断する仕組み。
設計思想をもとにしっかり開発され管理運営されています。
おわりに
私はWordPressとDrupalを使い、Webサイトを構築し運営しています。情報配信という一番シンプルで簡単なコンテンツでの利用ですが、それぞれの特徴や思想が面白く、その事がWebサイト運営を続ける理由にもなっています。
オープンソースには深い敬意があり、オープンソースのプロジェクトとして理想的な形を形成している、WordPressとDrupalについて感じている事を言葉でまとめたことで、私自身の理解が深まった出来事でした。