Codeigniter4が3と違いすぎて辛い
ついにCodeigniter4が正式リリースされたが、
などを読んで「・・・」となった方も多いのではなだろうか。
正直、Codeigniter3のプログラムの移行はほぼ諦めモード。
ただ、今後の新たに制作すものについては触らないわけにもいかないので、マニュアルに記載がなく(分かりづらく)、かつ、私がよく使っているやり方をCodeigniter4で再現するための方法を記載しておく。
コントローラ、メソッド名の取得
routerから取得できるコントローラの値が名前空間に変わってしまっている。
あまりスマートじゃないが、やや強引に解決。
Codeigniter 3
$controller = $this->router->fetch_class();
$method = $this->router->fetch_method();
Codeigniter 4
$router = \Config\Services::router();
$controller = $router->controllerName();
// App\Controllers\Indexといったnamespaceが取得されるので「\」で分割して最後を取得し、小文字にしている
$controller = explode('\\', $controller);
$controller = strtolower(end($controller)); // ex) index
// methodはこれだけ
$method  = $router->methodName(); 
コントローラで変数を設定してビューで使っていたものを利用できるようにする
コントローラ(神クラス)内で $this->hoge = 'fuga' として利用して、グローバルにビューで使えるようにいた場合。
自分は上記のコントローラ名とメソッド名をここに入れていた。
(WordPressのようにbodyに入れてCSSでページごとの出し分けをしていた)
神はもういない。
Codeigniter 3
- Controller(拡張コントローラにて設定の例)
class MY_Controller extends CI_Controller {
    public $hoge = '';
    public function __construct() {
        parent::__construct();
        $this->hoge = 'fuga';
    }
}
- View
<?= $this->hoge; ?>
Codeigniter 4
- ConfigのAppに変数を宣言
// 追記
public static $hoge  = '';
- 親コントローラに設定の例
namespace App\Controllers;
use CodeIgniter\Controller;
// use Config\App // App::$hoge でアクセスできるようにするなら追記 
class BaseController extends Controller
{
	protected $helpers = [];
	public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger)
	{
		// Do Not Edit This Line
		parent::initController($request, $response, $logger);
		
		// 追記
		Config\App::$hoge = 'fuga';
	}
}
- Controller
echo \Config\App::$hoge
- View
<?= Config\App::$hoge;?>
なお、これらは app/Config/View.php に記述し、 \Config\View::$hoge とすることもできる。
表示に関わる値を設定する場合はこちらに書くのも良いと思われる。
事前リダイレクト
URLヘルパにあった redirect('hoge') が使えなくなっており、コントローラの return(Response) として呼び出さなければならず、かなり戸惑う。
例えば、会員制サイトでログイン状況を見てログインしていなければログインページにリダイレクト、を例に取ると。
Codeigniter 3
- Controller(拡張コントローラにて設定の例)
class MY_Controller extends CI_Controller {
    public function __construct() {
        parent::__construct();
        // セッションにログイン情報がなければリダイレクト
        if(!$this->session->userdata('isLogin')) {
            redirect('login');
        }
    }
}
Codeigniter 4
- その1、素直にフィルタで設定
 参考:
 Thread Modes
 How to redirect from initController?
Codeigniter3で言うところのフックを作成する。
コントローラの読み込み前に実行されるため、上記のBaseControllerなどで処理した値は利用できない。
    // 該当箇所に追記する
	public $aliases = [
		'login' => \App\Filters\LoginFilter::class,  // エイリアス追加
	];
	public $globals = [
		'before' => [
			// すべてのページで利用する場合
			'login', 
			// 除外ページ(URIパターン)を指定する場合
			'login' => ['except' => ['index/*', 'login/*']],
		],
	];
<?php
namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class LoginFilter implements FilterInterface {
	public function before(RequestInterface $request) {
		$session = session();
		if (!$session->get('isLogin')) {
			// 除外ページを指定していない場合は除外ページ対象がどうかを判定してからリダイレクト
			return redirect()->to('/login');
		}
	}
}
- その2、BaseControllerのコンストラクタ(initController)で設定
リダイレクト前にあれこれしたいならこちら。
私はフィルタだと逆に面倒になる処理を作っているのでこちらを利用している。
class BaseController extends Controller {
	protected $helpers = [];
	public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger) {
		// Do Not Edit This Line
		parent::initController($request, $response, $logger);
		$router = \Config\Services::router();
		Config\App::$M = $router->methodName();
		$_controller = explode('\\', $router->controllerName());
		Config\App::$C = strtolower(end($_controller));
		// ここから追記
		$session = session();
		if (!$session->get('isLogin') && App::$C !== 'login') {
			$response = redirect()->to('/login');
			$response->send();
			exit();
		}
	}
}
コントローラのコンストラクタを拡張コントローラより後に利用する
上記で設定している拡張コントローラ(BaseController)をコントローラのコンストラクタの後に実行したい。
Codeigniter 3
拡張コントローラは標準ではMY_Controllerとなっているので、
class MY_Controller extends CI_Controller {
	public function __construct() {
		parent::__construct();
		// 先に行われるコンストラクタ処理
	}
}
class Hoge extends MY_Controller {
	public function __construct() {
		parent::__construct();
		// 後に行われるコンストラクタ処理
	}
}
CodeIgniter 4
ここまで読んでいれば分かると思うが、BaseController.php の initController を継承する必要がある。
普通にコンストラクタを利用してもエラーとなる(BaseControllerやシステムコントローラにコンストラクタが存在しないため)し、
(parent::__construct(); を削除すれば自分のコンストラクタとしては利用できる)
class BaseController extends Controller {
	public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger) {
		parent::initController($request, $response, $logger);
		// 先に行われるコンストラクタ処理
	}
}
class Hoge extends BaseController {
	public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger) {
		parent::initController($request, $response, $logger);
		// 後に行われるコンストラクタ処理
	}
}
フォームヘルパの初期値(value)にNULLを入れられなくなった
form_input('hoge', $this->input->get('fuga')) のように、初期値にNULLになり得る変数 を利用するケースがあるが、これはCodeIgniter4ではエラーとなる。
原因はvalue部にタイプヒンティングが追加されたため。
良し悪しは兎も角、これをそのまま使いたいなら、以下のようにタイプヒンティングを(デメリットを承知した上で)消してやれば良い。
CodeIgniter4
// system\Helpers\form_helper.php からコピーしてタイプヒンティングを消すだけ
function form_input($data = '', $value = '', $extra = '', string $type = 'text'): string {
	$defaults = [
		'type' => $type,
		'name' => is_array($data) ? '' : $data,
		'value' => $value,
	];
	return '<input ' . parse_form_attributes($data, $defaults) . stringify_attributes($extra) . " />\n";
}
form_textarea や form_password 等も同様に設定してやれば良い。
GET、POSTの値を上書きできない
CodeIgnoter3は都度 $_GET、$_POSTへアクセスしているため、上書きすると結果も変わるが、CodeIgniter4は最初にキャッシュしてしまうため、上書きしても変わらなず、再度キャッシュし直す必要がある。
CodeIgnoter3
echo $this->input->get('hoge');  // hoge
$_GET['hoge'] = 'fuga';
echo $this->input->get('hoge');  // fuga
CodeIgniter4
// 詳しくは system\HTTP\Request.php を参照
echo $this->request->getGet('hoge'); // hoge
$_GET['hoge'] = 'fuga';
echo $this->input->get('hoge');  // hoge(変わらない)
$this->request->setGlobal('get', $_GET);  // GETを再設定
// $this->request->setGlobal('post', $_POST);  // POSTを再設定
echo $this->request->getGet('hoge'); // fuga(変わった!)
Viewの内容を変数で受け取る
CodeIgnoter3
$hoge = $this->load->view('fuga', $piyo, true);
CodeIgniter4
trueとか必要なくなった。
(逆に出力するときに echo しなければならない)
$hoge = view('fuga', $piyo);
ここまではマニュアルに書いてあるが、development環境ではデバッグコードがHTMLのコメントとして取得されてしまうので、Codeigniter 4 でビューを変数に代入するときにデバッグコードが出ないようにする 必要がある。
return を返せないときのリダイレクト
CodeIgnoter3
// どんなときでもこれで行ける
$this->load->helper('url');
redirect('path/to');
CodeIgniter4
単純に書き換えると以下のURLに従い
https://codeigniter4.github.io/userguide/general/common_functions.html?highlight=redirect#redirect
// コントローラー内ならこれだけ
return redirect()->to('/path/to');
とすればリダイレクトができるが、例えば共通メソッド(private function)だったりモデル内でリダイレクトしたいという需要もある。
つまり、単純にreturnができない(コントローラーを終了できない)場面。
実はこれはエラーハンドリング側にマニュアルがある。
https://codeigniter4.github.io/userguide/general/errors.html?highlight=redirectexception
// returnが返せないときのリダイレクト
throw new \CodeIgniter\Router\Exceptions\RedirectException('/path/to'); // 302
throw new \CodeIgniter\Router\Exceptions\RedirectException('/path/to', 301); // 301
面倒ならこちらだけ使えば統一できる(エラーハンドリングだらけになるのを気持ち的に許容できれば)
