CodeIgniterでエラーページを良い感じに構築したい

# 環境
PHP7
CodeIgniter 3.1.11

# 概要
CodeIgniterで404ページを編集する場合は、デフォルトでは以下のファイルを編集することになるかと思います。

`application/view/errors/html/error_404.php`

ただ、これでちょっとした不都合があります。

サイトが下図のような構成とします。
![プレゼンテーション1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241128/f2450f66-306f-c120-2201-6275173e413d.jpeg)

エラーページは以下のように表示したいとします。
![プレゼンテーション12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241128/83d24e0a-3c7b-f421-6390-97f59666f446.jpeg)

そうすると、前述のエラーページ用のファイルにヘッダーから何から何まで、すべて記述する必要があります。
![プレゼンテーション13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241128/c6c9df3f-cd01-9f4a-ee01-3ad21f3d179d.jpeg)

また、後述しますが、動的なコンテンツへの対応が難しくなります。

本記事では、これらの不都合に対して、解決策と記述のサンプルをまとめます。

※別にこれらに困っていない人は、YouTubeでペットの動画を見ている方が有意義ですので今すぐ閉じてください。

# 解説の前に
まず、概要で記載した不都合が、そもそもなぜ不都合なのかという部分。

共通パーツの部分をpartial化してincludeすれば、html記述自体の重複は防げるのですが、includeするファイル名の変更があった場合に、しばしばエラーページ側の変更が漏れてしまうことがあります。

これだけなら気を付けばなんとかなりそうなのですが、さらに大きな問題がありまして、なんとコントローラ中のshow_404()でなくルーティングの時点で404になった場合に、CIコントローラのインスタンスが生成されないのです。

文章で書くと至極当然な気がしてきましたが、これにより、includeするファイルで本来設定されているはずのパラメータや、呼び出せるはずの関数が使えない、といったことになります（特に、MY_Controller.phpで拡張している場合）。

これは、データベースにメニュー構成などを保存している場合に、それらを呼び出したい際にも障害になります。

これらを解消するために行ったものから、最低限の内容を以降で解説します。


# 実装サンプル
```application/core/MY_Exceptions.php
<?php
defined('BASEPATH') or exit('No direct script access allowed');

class MY_Exceptions extends CI_Exceptions
{
    public function __construct()
    {
        parent::__construct(); 

        $this->CI =& get_instance();
    }

    public function show_404($page = '', $log_error = TRUE)
    {
        // ルーティングの時点で404になった場合はインスタンスが生成されていないので、手動で呼び出す
        if (empty($this->CI)) {
            require_once APPPATH . 'core/MY_Controller.php';
            $this->CI = new MY_Controller();
        }

        // ヘッダーにエラーステータスをセット
        $this->CI->output->set_status_header('404');

        // 共通コンテンツのデータをセット
        $args = $this->CI->get_common_contents();

        // エラーページ用の本文を取得
        $args['html'] = $this->CI->load->view("errors/error_404.php", [], true);

        // viewの出力
        $this->CI->load->view("layout/index.php", $args);
    }
}
```

# 解説

application/
　├ core/
　│ 　├ MY_Controller.php
　│ 　└ MY_Exceptions.php
　└ views/
　　 　├ layout/
　　 　│ 　├ index.php
　　 　│ 　└ partial/ （これらの説明は割愛）
　　 　│ 　　 　├ header.php
　　 　│ 　　 　├ footer.php
　　 　│ 　　 　└ sidebar.php
　　 　└ errors/error_404.php

最初に、MY_Controllerを作成して、メニューなどの共通コンテンツ一式を取得する部分をfunctionにまとめておきましょう（本記事では、get_common_contentsとしています）。

次に、CodeIgniterの通常のエラー処理を拡張するために、MY_Exceptionsを用意します。

```
public function show_404($page = '', $log_error = TRUE)
{
    /* ここに処理を記載 */
}
```

エラー処理は、CI_Exceptionsのshow_404をオーバーライドするかたちで実装します。

```
if (empty($this->CI)) {
    require_once APPPATH . 'core/MY_Controller.php';
    $this->CI = new MY_Controller();
}
```

コントローラ上でshow_404を呼び出した場合は、コンストラクタのget_instance()でCIコントローラを取得できるのですが、ルーティングの時点で404になった場合はまだインスタンスが生成されていないので、このように手動で呼び出す必要があります。

```
$this->CI->output->set_status_header('404');
```
レスポンスヘッダーに404をセットしましょう。


```
$args = $this->CI->get_common_contents();
```
MY_Controllerに用意しておいた、共通コンテンツ一式を取得するやつです。
この$argsが、viewの出力に渡す引数とします。

```
$args['html'] = $this->CI->load->view("errors/error_404.php", [], true);
```

CI_Controllerのload->viewの第三引数にtrueをセットすると、printせずにstringでリターンしてくれるので、これを$argsに含めておきます。

```
$this->CI->load->view("layout/index.php", $args);
```

`application/views/layout/index.php`に、ヘッダーなどのincludeを含めつつ、本文の部分は`<?php echo $html; ?>`を入れておけば良いです。

# まとめ
久しぶりの投稿のせいか、かなり語彙力の衰えを感じました。

とくにタイトルがヤバイ。