1つのCodeigniterで複数サイト（例：フロントと管理画面）を運用する

【注意】

- あくまで同一サーバーで運用するケースを想定しています。
- これを行うと spark コマンドでのテスト、運用はできなくなります。

----

# 管理画面をつくりたい、けど、複数のFWをインストールしたくない

ECサイト、ポータルサイトなど会員制のWebサイトには大抵の場合（DBを直接いじるとかでなければ）管理画面が必要になる。
その場合、運用方法の多くは

1. フロントをドキュメントルートで、管理画面をサブディレクトリで
2. フロントと管理画面を別ドメインで

の2択となるが、セキュリティ的にも後者の方が望ましいケースが多い。
（管理者画面を運営会社内からのアクセスに限定したりBasic認証をかけたりすることが多いし、サブディレクトリよりもアタリをつけられにくい）

この場合、**モデルやヘルパ、ライブラリの殆どはフロント、管理画面ともに使いまわしになるケースが多い** ため、複数のCodeIgnier（FW）をインストールする（モデルやヘルパをそれぞれに用意する）のは望ましくない。
そこで、今回は私の運用方法を紹介する。


## プロジェクト構成

今回は仮にこのような構成とする

- path_to_project/projectname
    - front（hoge.com）  
フロントサイトのドキュメントルート  
    - admin（admin.hoge.com）  
管理画面のドキュメントルート
    - static（static.hoge.com）  
CSSやJS、画像の配信用のドキュメントルート  
ここではCodeIgnierは動かさない
    - app/Controllers/Front  
フロントサイト用コントローラ 
    - app/Views/Front  
フロントサイト用ビュー 
    - app/Controllers/Admin  
管理画面用コントローラ 
    - app/Views/Admin  
管理画面用ビュー 


## 1. CodeIgnierのインストールとディレクトリ構成を作成

`composer create-project codeigniter4/appstarter projectname` や `composer require codeigniter4/framework` などで表示できるようにする。

``` shell
# create-project を利用する場合
cd /path_to_project
composer create-project codeigniter4/appstarter projectname

# ドキュメントルートとして `public` が設定されるので名前変更
cd projectname
mv public front

# 管理画面用と配信用ディレクトリも準備
cp -rp front admin
mkdir static

# ---------------

# require を利用する場合
cd /path_to_project
mkdir projectname
cd projectname
composer require codeigniter4/framework

# 必要なファイルをプロジェクトルートにコピー
cp -rp vendor/codeigniter4/framework/app ./
cp -rp vendor/codeigniter4/framework/public ./front
cp -rp vendor/codeigniter4/framework/writable ./
cp -rp vendor/codeigniter4/framework/env ./.env
cp -rp vendor/codeigniter4/framework/phpunit.xml.dist ./

# 管理画面用と配信用ディレクトリも準備
cp -rp front admin
mkdir static

# app/Config/Paths.php の $systemDirectory の書き換えを忘れずに
vi app/Config/Paths.php

# - public $systemDirectory = __DIR__ . '/../../system';
# + public $systemDirectory = __DIR__ . '/../../vendor/codeigniter4/framework/system';
```

ログやセッションファイルなどが入る writable もサイトごとに分けて用意する。

```shell
mkdir writable/Front
cp -rp writable/cache writable/Front/
cp -rp writable/debugbar writable/Front/
cp -rp writable/logs writable/Front/
cp -rp writable/session writable/Front/
cp -rp writable/uploads writable/Front/
cp -rp writable/Front writable/Admin
```

また、ドキュメントルートのindex.phpの頭に画面分けの定数を設定しておく。
※ここで設定する `SITE_DIRECTORY` の値は設定ファイル（app/Config/constants.php）の読み込みよりも前に利用するため、ここに記述する必要がある。
※envは共通設定しかできないため、ここでは利用しない。

``` front/index.php
<?php
define('IS_ADMIN', false);
define('SITE_DIRECTORY', 'Front');
```


``` admin/index.php
<?php
define('IS_ADMIN', true);
define('SITE_DIRECTORY', 'Admin');
```

環境設定を開発環境に設定しておく

``` .env
CI_ENVIRONMENT = development
```

## 2. ドメインを通す

Webサーバーの設定方法は省略。
以下になるように設定し、Webサーバーをリロード（再起動）する。

- hoge.com  
→ /path_to_project/front
- admin.hoge.com  
→ /path_to_project/admin
- static.hoge.com  
→ /path_to_project/static

設定後、各ドメインへアクセスし、
`hoge.com` と `admin.hoge.com` には `Welcome to CodeIgniter 4.x.x` と表示され、
`static.hoge.com` には何も表示されない（※）ことを確認。
※ 必要に応じてindex.html や index.php を置いて確認する。

## 3. コントローラ作成

```shell
mkdir app/Controllers/Front
mkdir app/Controllers/Admin
```

``` app/Controllers/Front/Home.php
<?php namespace App\Controllers\Front;

class Home extends BaseController
{
	public function index()
	{
		return view('welcome_message');
	}
}
```

``` app/Controllers/Front/BaseController.php
<?php
namespace App\Controllers\Front;
use CodeIgniter\Controller;

class BaseController extends Controller
{
	protected $helpers = [];
	public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger)
	{
		parent::initController($request, $response, $logger);
	}

}
```


``` app/Controllers/Admin/Home.php
<?php namespace App\Controllers\Admin;

class Home extends BaseController
{
	public function index()
	{
		return view('welcome_message');
	}
}
```

``` app/Controllers/Admin/BaseController.php
<?php
namespace App\Controllers\Admin;
use CodeIgniter\Controller;

class BaseController extends Controller
{
	protected $helpers = [];
	public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger)
	{
		parent::initController($request, $response, $logger);
	}

}
```

## 4. ビュー作成

とりあえず `welcome_message.php` を利用する場合として説明する。
また、エラーページもサイトごとに変えられるようにしておく。

```shell
mkdir app/Views/Front
mkdir app/Views/Admin
cp app/Views/welcome_message.php app/Views/Front/
cp app/Views/welcome_message.php app/Views/Admin/
cp -rp app/Views/errors app/Views/Front/errors 
cp -rp app/Views/errors app/Views/Admin/errors 

# 目印のために一部の文字列を書き換えて置く
sed -i 's/The small framework with powerful features/This is Front Site/g' app/Views/Front/welcome_message.php
sed -i 's/The small framework with powerful features/This is Admin Site/g' app/Views/Admin/welcome_message.php
```


## 5. 設定ファイル更新

これがちょっと面倒。
もっといい方法はあると思うけど、ひとまず定数を設定しまくって対応する。


``` app/Config/Constants.php
# 共通設定を追記（この部分はenvファイルでも良いが、感覚的にこっちのほうが楽）
defined('FRONT_URL') || define('FRONT_URL', 'http://hoge.com');
defined('ADMIN_URL') || define('ADMIN_URL', 'http://admin.hoge.com');

# サイトのURLとディレクトリ名を保持しておく
if (IS_ADMIN) {
	defined('BASE_URL') || define('BASE_URL', ADMIN_URL);
} else {
	defined('BASE_URL') || define('BASE_URL', FRONT_URL);
}
```

``` app/Config/App.php
public $baseURL = BASE_URL; // 上記で設定したサイトごとのベースURL
```

``` app/Config/Paths.php
public $writableDirectory = __DIR__ . '/../../writable/' . SITE_DIRECTORY;
public $viewDirectory = __DIR__ . '/../Views/' . SITE_DIRECTORY;
```

``` app/Config/Routes.php
$routes->setDefaultNamespace('App\Controllers\\' . SITE_DIRECTORY);
```

## 6. アクセスチェック

- http://hoge.com  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104999/258fb536-141b-515c-0cad-a03e22a5b768.png)
- http://admin.hoge.com  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104999/62c963af-4464-e24a-716f-c3d0211cebd6.png)


## あとは普通に開発していくだけ

モデルやライブラリ、ヘルパ、環境設定（.envやconstants.php）などは共通のままで、コントローラ、ビューだけを別々に管理すれば良く、更にwritableが分かれていることによりセッションもぶつからないし、ログも別々に出力されので、だいぶメンテナンスコストを軽減できる。

この方法を使い回すと追加でクライアント企業用画面とか、APIなどをドメインごとに実装することも可能で、ベースのプログラムを一元化したまま、サイトごとにサーバー側の設定やBaseControllerなどでセキュリティを実装したりもできるので私はこの方法をCodeigniter3時代から愛用している。

ちなみに、この応用でサブディレクトリの運用も可能だが、ファイルアップロードを実装する場合にパスの階層設定が変わるので少し面倒になる。

## おまけ：フロントと管理画面でモデルの利用の仕方が微妙に違う場合

サイトごとに同一メソッドで違う値を取得したいケースがママある。
その場合はモデルを継承して対象メソッドを上書きして利用すれば良い

``` app/Models/FugaModel.php
<?php
namespace App\Models;
use CodeIgniter\Model;

class FugaModel extends Model {

	protected $table = 'fuga';
	protected $primaryKey = 'fuga_no';
	protected $returnType = 'array';
	protected $useSoftDeletes = true;
	protected $allowedFields = [];
	protected $useTimestamps = true;
	protected $createdField = 'created_at';
	protected $updatedField = 'updated_at';
	protected $deletedField = 'deleted_at';
	protected $validationRules = [];
	protected $validationMessages = [];
	protected $skipValidation = false;
	protected $afterFind = [];

	public function piyo(){
		// フロント用のプログラム
		return 'piyo';
	}
}
```

``` app/Models/FugaAdminModel.php
<?php
namespace App\Models;
use CodeIgniter\Model;

class FugaAdminModel extends FugaModel {
	public function piyo(){
		// 管理画面用のプログラム
		return 'piyopiyo';
	}
}
```

または、FugaModelを共通用に設定しておき、FugaFrontModelを別途作成するのも良い。
これは例えば、UserModel を作った場合に、

- ユーザー（フロント）画面からアップデートできる項目と管理者からアップデートできる項目が異なる（$allowedFields が異なる）
- フロント側にはログインメソッドがある
- 管理者側でユーザーを取得したときに付帯情報を追加する（$afterFind の設定）

といった使い分けが可能になる

## おまけ： static.hoge.com の存在について

静的ファイルのみを配置する。
そのため、キャッシュを長めに設定すると良い。
特にファイルアップロードを実装した場合にfrontやadminの中にファイルが作成されてしまうのはよろしくないので、別途ドメインを作成して配置するようにしている。
ちなみに私はコーディングには webpack（babel、Vue、sassなど） を利用して、トランスパイルをするとstatic内に配置されるように設定している。
※ ただし、CDNが利用できる場合は不要。