Vue.jsのSFC（単一ファイルコンポーネント）＋PHPでWebアプリケーションを構築する - バックエンド環境編

# 前回の記事はこちら

本記事は、[『フロントエンド環境編](https://qiita.com/ison12/items/b336766bd6ca01cd62a3)』の続きです。

# バックエンド側の環境構築

タイトルにある通り、バックエンドはPHP言語を利用します。
また、著者はWindows10で環境構築をしています。

## ソースのルートディレクトリについて

前回に引き続き、`C:\hello-vuejs`を前提に話を進めます。

## インストールライブラリのまとめ

バックエンド側で必要なものは以下の通りです。

| ソフトウェア | バージョン | 説明 |
|:-----------------|------------------:|:------------------|
| [Apache](https://httpd.apache.org/) | 2.4.* | Webサーバー |
| [PHP](https://www.php.net/manual/ja/) | 7.4.* | バックエンド言語 |
| [Slim Framework](http://www.slimframework.com/docs/v3/) | 3.1.* | PHPフレームワーク |
| [Slim PHP-View](http://www.slimframework.com/docs/v3/features/templates.html#the-slimphp-view-component) | ^2.2 | PHPフレームワーク |
| [Monolog](https://github.com/Seldaek/monolog) | ^2.0 | PHPロガー |

## Xamppのインストール
ApacheとPHPを使いたいので、XAMPPをインストールしました。本記事は、XAMPPベースで説明を進めます。
また、XAMPPの7.4系（しっかり確認していませんが、PHPの7.x系なら全般的に動作可能な気がします）をチョイスしました。

[XAMPPのページ](https://www.apachefriends.org/jp/index.html)

**HINT**
XAMPPのインストールを進めると、コンポーネント選択画面が表示されます。
本記事で必要なものは、以下の通りです（最小限の指定）。

XAMPPインストールウィザード - Select Components
![xampp_select_components.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609134/4ed63a90-48c6-c076-8845-53f05a30ea6f.png)

**注意**
著者の環境（OSはWindows10）では、インストール完了後に、XAMPPコントロールパネルを起動する際、`"Error:Cannot create file "[XAMPPインストールパス]\xampp-control.ini". Access denied.`と表示されました。
エラーを解消するには、XAMPPインストールパスの`xampp-control.exe`を管理者権限で起動（右クリックして管理者権限で起動）します。以後は通常の操作で起動できるようになりました。

## Apacheの設定

### ポート番号の変更
Apacheを起動すると、httpは80番、httpsは443番として、デフォルトのポート番号が割り当てられています。
デフォルトのままだと、他のアプリとポート番号が衝突する可能性があるので変更することにします。
本記事では、httpを5555番、httpsを5556番に変更しました。

```INI:[XAMPPインストールパス]\apache\conf\httpd.conf
# Listenの設定
Listen 80
↓
Listen 5555

# ServerNameの設定
ServerName localhost:80
↓
ServerName localhost:5555
```

```INI:[XAMPPインストールパス]\apache\conf\extra\httpd-ssl.conf
# Listenの設定
Listen 443
↓
Listen 5556

# VirtualHostの設定
<VirtualHost _default_:443>
↓
<VirtualHost _default_:5556>

# ServerNameの設定
ServerName www.example.com:443
↓
ServerName www.example.com:5556
```

### 公開ディレクトリの設定
`hello-vuejs`の`public`ディレクトリをApacheの公開ディレクトリとして指定します。
公開ディレクトリの指定には幾つかありますが、ここでは、`alias_module`を利用します。

```INI:[XAMPPインストールパス]\apache\conf\httpd.conf
...
<IfModule alias_module>
    ...

    Alias /hello-vuejs "C:\hello-vuejs\public"
    <Directory "C:\hello-vuejs\public">
        AllowOverride All
        Require all granted
    </Directory>

</IfModule>
...
```

**HINT**
alias_moduleを使用すると、URLが ”http://[ドメイン]/hello-vuejs” のように1段階層が深くなります。
virtual_hostを使用するというのも良い選択肢かと思います。

## PHPの設定

PHPの挙動を設定するファイルは、`[XAMPPインストールパス]\php\php.ini`です。
今回は、デフォルト設定のままで問題ないので特に編集しません。

## Apacheの起動

XAMPPのコントローラパネルからApacheを起動しましょう。下図のような画面になればOKです。

![hello-vuejs2_xampp_control_panel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609134/4e4e6677-a5bf-9481-2cc7-6983d06527ab.png)


## Composerで必要なパッケージを導入する

PHPの各種ライブラリを入手するために、Composerをインストールするところから始めます。
以下のページから最新版をダウンロードしてインストールしましょう。

[Composerのページ](https://getcomposer.org/)

**HINT**
ComposerはPHPのパッケージ管理システムです。こちらを使って、PHPの各種ライブラリを導入します。

### Composerの初期化
コマンドプロンプトを開いて、`C:\hello-vuejs`に`cd`しましょう。
そして、最初に以下を実行してください。
色々聞かれますが、大体の質問にEnterを押して、最後にyesを入力して行けばとりあえずOKです。

詳細が知りたい人は、ググってみてください。

```DOS:コマンドプロンプト
composer init

...

# パッケージ名は、<vendor>/<name>形式の必要があるので、"ison/hello-vuejs"と入力
Package name (<vendor>/<name>) [***/***]: ison/hello-vuejs

... その他の質問は適当にEnterとyesを入力する

```

### Composerのパッケージインストール

次に、以下を実行して行きます。

```DOS:コマンドプロンプト
composer require slim/slim:3.*
composer require slim/php-view
composer require monolog/monolog
```

**HINT**
`composer require`は、npmで言うところの`npm install`と同じものです。
著者の環境では、コマンドを打ち込んでからしばらく反応が無く3分以上待たされてしまいました。気長に待ちましょう。

### Autoloadの設定

次に、composer.jsonの`"autoload"`部分を追記します。

```JavaScript:composer.json
{
    ...
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "config": {
        "optimize-autoloader": true
    }
}
```

ファイルの編集が終わったら、以下を実行します。

```DOS:コマンドプロンプト
composer dumpautoload
```

[autoload](https://getcomposer.org/doc/04-schema.md#autoload)は、PHPでモジュールファイルをロードする仕組みの事です。
簡単に説明すると、上の設定はsrcディレクトリに配置されるPHPのクラスファイルは、Appという名前空間で始まるという事を意味します。

また、最適化のために、`"optimize-autoloader": true`というオプションも有効にしました。
詳細は、以下のページを確認してください。
[オートローダーの最適化](https://getcomposer.org/doc/articles/autoloader-optimization.md)

**HINT**
クラスの追加や変更があった場合に、オートローダーの最適化を再適用するコマンドは以下の通りです。

```DOS:コマンドプロンプト
composer dumpautoload           ... 最適化あり
composer dumpautoload --no-dev  ... 最適化ありで開発環境以外にリリース
```

バージョン管理システムからチェックアウトした直後などは、単に`composer install [--no-dev]`するだけでOKです。

composer.jsonに`"optimize-autoloader": true`を記述しない場合は、`composer dumpautoload -o [--no-dev]`、`composer install --optimize-autoloader [--no-dev]`とすればOKです。

---

全て実行し終えると、以下のようなファイルが出来上がっているはずです。

```:ファイル構成
c:\hello-vuejs
    + vendor
    + composer.json
    + composer.lock
```

vendorには、インストールしたライブラリファイルが格納されています。こちらは、バージョン管理が不要なディレクトリです。

composer.jsonには、composer initした際の各種設定と、何をインストールしたかの情報が記載されています。
composer.lockには、インストール時の厳密なライブラリのバージョン番号のスナップショットが記載されています。
こちらの2ファイルはコミットが必要です。

他の開発者は、こちらのcomposer.jsonまたはcomposer.lockから、composer installを実行して必要なライブラリをインストールすることになります。
したがって、本節で実施した、composer initとcomposer require [ライブラリ名]は、一度限りの実行になります。

他の開発者は、バージョン管理ツールからチェックアウトしたソースに対して、composer installを実行するだけで必要なライブラリを揃えることができます。

## index.php、.htaccess、bootstrap.phpの作成

Webリクエストを受信した際にPHPを動作させるために必要な起動用のファイルを作成します。

```:ファイル構成
c:\hello-vuejs
    ...
    + public
        + index.php
        + .htaccess
    + src
        + bootstrap.php
```

Webリクエストを受信すると、以下の順序で処理が行われます。

1. .htaccessの実行（リクエストされたファイルが存在しない場合に、mod_rewriteの書き換えルールが働きindex.phpが実行される）
2. index.phpの実行（index.phpの先頭の処理でbootstrap.phpが読み込まれる。index.phpはURLの内容からコントローラークラスと実行すべきメソッドを特定して実行する）

---

.htaccessの内容は以下の通りです。
mod_rewriteというURL書き換えモジュールの適用と、PHP設定の初期化です。

```ini:.htacccess
<IfModule mod_rewrite.c>
  RewriteEngine On

  # Some hosts may require you to use the `RewriteBase` directive.
  # Determine the RewriteBase automatically and set it as environment variable.
  # If you are using Apache aliases to do mass virtual hosting or installed the
  # project in a subdirectory, the base path will be prepended to allow proper
  # resolution of the index.php file and to redirect to the correct URI. It will
  # work in environments without path prefix as well, providing a safe, one-size
  # fits all solution. But as you do not need it in this case, you can comment
  # the following 2 lines to eliminate the overhead.
  RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
  RewriteRule ^(.*) - [E=BASE:%1]

  # If the above doesn't work you might need to set the `RewriteBase` directive manually, it should be the
  # absolute physical path to the directory that contains this htaccess file.
  RewriteBase /hello-vuejs

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^ index.php [QSA,L]
</IfModule>

php_value "display_errors" "On"
php_value "error_reporting" "E_ALL"

php_value "max_execution_time" "0"

php_value "memory_limit" "1024M"
php_value "post_max_size" "1024M"
php_value "upload_max_filesize" "5G"
php_value "max_input_vars" "10000"
php_value "date.timezone" "Asia/Tokyo"
```

---

index.phpの内容は以下の通りです。
前述で作成したbootstrap.phpのロードと、HTTPリクエストのルーティング設定を行います。

例えば、`http://[ドメイン]/hello-vuejs/hello`を実行すると`src\Func\Hello\Controller\HelloController.php`の`actionIndex`が実行されます。

```PHP:index.php
<?php

use Slim\Exception\NotFoundException;
use Slim\Http\Request;
use Slim\Http\Response;

$app = require __DIR__ . '/../src/bootstrap.php';

$app->any('/[{func1}[/{func2}[/{func3}]]]', function (Request $request, Response $response, array $args) use($app) {

    $func1 = $args['func1'] ?? null;
    $func1 = ucfirst($func1);
    $func2 = $args['func2'] ?? null;
    $func2 = ucfirst($func2);
    $func3 = $args['func3'] ?? null;
    $func3 = ucfirst($func3);

    $class = '';
    $method = '';
    $pattern = null;

    if ($func1 && $func2 && $func3) {
        // 例）/login/auth/exec → Login\Controller\AuthController#actionExec
        $pattern = 1;
        $class = '\\App\\Func\\' . $func1 . '\\Controller\\' . $func2 . 'Controller';
        $method = 'action' . $func3;
    } else if ($func1 && $func2) {
        // 例）/login/exec → Login\Controller\ExecController#actionIndex
        $pattern = 2;
        $class = '\\App\\Func\\' . $func1 . '\\Controller\\' . $func2 . 'Controller';
        $method = 'actionIndex';

        if (!class_exists($class)) {
            // 例）/login/exec → Login\Controller\LoginController#actionExec
            $class = '\\App\\Func\\' . $func1 . '\\Controller\\' . $func1 . 'Controller';
            $method = 'action' . $func2;
        }
    } else if ($func1) {
        // 例）/login → Login\Controller\LoginController#actionIndex
        $pattern = 3;
        $class = '\\App\\Func\\' . $func1 . '\\Controller\\' . $func1 . 'Controller';
        $method = 'actionIndex';
    }

    if (!class_exists($class)) {
        throw new NotFoundException($request, $response);
    }

    $controller = new $class($app);
    if (!method_exists($controller, $method)) {
        throw new NotFoundException($request, $response);
    }

    try {
        return $controller->$method();
    } catch (Throwable $exc) {
        $GLOBALS['unexpectedException'] = $exc;
        throw $exc;
    }
});

// Run app
$app->run();
```

---

bootstrap.phpの内容は以下の通りです。
PHPのフレームワークであるSlim関連のインスタンスを生成しています。

```PHP:bootstrap.php
<?php

use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Monolog\Processor\UidProcessor;
use Slim\App;
use Slim\Views\PhpRenderer;

const PUBLIC_PATH = __DIR__ . '/../public/';
const SRC_PATH = __DIR__ . '/';

$app = null;

/*
 * Load the autoloader
 */
$autoloader = require __DIR__ . '/../vendor/autoload.php';

// Create Slim App instance
$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
    ],
]);

/*
 * Set up dependencies
 */
$container = $app->getContainer();

// View Renderer
$container['renderer'] = function ($c) {
    $templatePath = SRC_PATH;
    return new PhpRenderer($templatePath);
};

// monolog
$container['logger'] = function ($c) {
    $settings = $c->get('settings')['logger'];
    $logger = new Logger('app');
    $logger->pushProcessor(new UidProcessor());

    $handler = new RotatingFileHandler(__DIR__ . '/../log/app.log', 0, \Monolog\Logger::DEBUG, true, 0664);
    $handler->setFilenameFormat('{date}-{filename}', 'Y/m/d');
    $logger->pushHandler($handler);

    return $logger;
};

return $app;
```

## Controllerクラスの作成とHelloコンポーネントの修正

前述で作成したindex.phpから実行されるコントローラクラスを作成します。
Helloコンポーネントも合わせて修正します。

```:ファイル構成
c:\hello-vuejs
    ...
    + src\Func
        + Base\Controller
            + BaseController.php     ... Hello・Goodbyeなどの各コントローラの基底クラス
        + Goodbye\Controller
            + GoodbyeController.php  ... Goodbyeのコントローラ
        + Hello
            + Controller
                + HelloController.php    ... Helloのコントローラ
            + Front\View                 ... Helloコンポーネント（htmlとjsのみ修正）
                + Hello.html
                + Hello.js
```

---

BaseController.phpの内容は以下の通りです。

特に重要なのは、renderメソッドです。
このメソッドで、`src\View\Layout\Base.php`内のHTMLテンプレートをレスポンスに出力しています。

```PHP:BaseController.php
<?php

namespace App\Func\Base\Controller;

use App\Common\DB\DBObserver;
use App\Common\Exception\ServiceException;
use App\Common\Log\AppLogger;
use App\Common\Message\MessageManager;
use App\Common\ResponseCache\ResponseCache;
use App\Common\Session\SessionData;
use App\Common\Util\DBUtil;
use App\Constant\CommonConstant;
use App\Func\Login\Service\LoginService;
use SebastianBergmann\RecursionContext\Exception;
use Slim\App;
use Slim\Container;
use Slim\Exception\NotFoundException;
use Slim\Http\Response;
use Slim\Http\Stream;
use const SRC_PATH;

/**
 * 基本となるコントローラークラス。
 * コントローラクラスは、本クラスを継承すること。
 */
class BaseController {

    /**
     * @var App Appオブジェクト
     */
    protected $app;

    /**
     *
     * @var Container Containerオブジェクト
     */
    protected $container;

    /**
     * @var AppLogger ロガー
     */
    protected $logger;

    /**
     * @var string レイアウト名
     */
    protected $layoutName;

    /**
     * @var array リクエストパラメータ
     */
    protected $requestParams;

    /**
     * コンストラクタ。
     * @param App $app アプリケーションオブジェクト
     */
    public function __construct(App $app) {

        $this->app = $app;
        $this->container = $app->getContainer();
        $this->logger = $this->container['logger'];

        $this->layoutName = 'Base';
        $this->requestParams = null;

    }

    /**
     * リクエストパラメータを取得する。
     * @return array リクエストパラメータ
     */
    protected function getRequestParams(): array {

        if ($this->requestParams !== null) {
            return $this->requestParams;
        }

        $request = $this->container->request;

        $getParams = $request->getQueryParams() ?? [];
        $postPutParams = $request->getParsedBody() ?? [];

        $allParams = array_merge($getParams, $postPutParams);
        $this->requestParams = $allParams;

        return $allParams;
    }

    /**
     * 描画メソッド。
     * @param string $contentsViewPath コンテンツビューのパス … /srcディレクトリからのパスを指定する（例：/Func/Login/Front/View/Login）
     * @param array $contentsData データ
     * @param string $layoutName レイアウトページ名 … /src/View/Layout下のレイアウトページを指定する（例：Base.phpの場合は、"Base"と指定する）
     * @param array $importWidget インポートするWidget
     * @return type
     */
    protected function render(string $contentsViewPath, array $contentsData, string $layoutName = null) {

        if (!file_exists(SRC_PATH . $contentsViewPath . '.vue')) {
            // ファイルが存在しないため、例外を発行する（基本的には発生しないエラー）
            throw new \Exception("コンテンツビューファイル未存在エラー contentsViewPath={$contentsViewPath}");
        }

        $contentsViewName = basename($contentsViewPath);

        // レイアウトページの設定
        if ($layoutName === null) {
            $layoutName = $this->layoutName;
        }
        $layoutPhp = '/View/Layout/' . $layoutName . '.php';

        // コンテナから関連するオブジェクトを取得
        $container = $this->container;
        $request = $container->request;
        $response = $container->response;
        $renderer = $container->renderer;
        $baseUrl = $request->getUri()->getBasePath();

        // リクエストパラメーターを取得する
        $requestParams = $this->getRequestParams();

        // レイアウトページのデータオブジェクト
        $layoutDataObj = [
            '__baseUrl' => $baseUrl,
            '__requestParams' => $requestParams,
            '__contentsViewPath' => $contentsViewPath,
            '__contentsViewName' => $contentsViewName,
            '__contentsData' => $contentsData,
        ];

        return $renderer->render($response
                        , $layoutPhp
                        , $layoutDataObj);
    }

    /**
     * Json出力メソッド。
     * @param mixed $data The data
     * @param int $status The HTTP status code.
     * @param int $encodingOptions Json encoding options
     * @return type
     */
    protected function renderJson($data, int $status = null, int $encodingOptions = 0) {

        $response = $this->container->response;

        return $response->withJson($data, $status, $encodingOptions);
    }

}
```

---

HelloController.phpで重要なのは、`actionIndex`メソッドです。
内部では、`BaseController#render`メソッドを呼び出しています。引数にHelloコンポーネントのパスを渡すことで、最終的にVue.jsのHelloコンポーネントを表示するように指示しています。

また、Helloコンポーネント上のボタン押下時のajax通信に対応するために、`actionTalk`メソッドを設けました。こちらは、`BaseController#renderJson`メソッドで`talkMessage`データをレスポンスとして返却しています。

```PHP:HelloController.php
<?php

namespace App\Func\Hello\Controller;

use App\Func\Base\Controller\BaseController;
use Slim\App;

/**
 * Helloコントローラー。
 */
class HelloController extends BaseController {

    /**
     * コンストラクタ。
     * @param App $app アプリケーションオブジェクト
     */
    public function __construct(App $app) {
        parent::__construct($app);
    }

    /**
     * 表示処理。
     */
    public function actionIndex() {

        return $this->render('/Func/Hello/Front/View/Hello', []);
    }

    /**
     * 話す処理。
     */
    public function actionTalk() {

        $params = $this->getRequestParams();

        $data = ['talkMessage'=>"Hi, I came from space."];

        return $this->renderJson($data);
    }
}
```

Helloコンポーネントのhtmlとjsファイルも修正します。
修正内容は、ボタンの追加・ボタン押下時の処理・返却メッセージの表示領域の追加です。

```html:Hello.html
<div class="hello-component" v-cloak ref="view">

    <div>
        This is Hello Component.<br>
        {{message}}
    </div>

    <div>
        <button type="button" @click="talk">Hello?</button>
    </div>

    <div>
        <p>{{talkMessage}}</p>
    </div>

</div>
```

``` javascript:Hello.js

export default {
    data() {
        return {
            message: 'Hello Vue.js',
            talkMessage: ''
        };
    },
    methods: {
        talk() {

            $.ajax({
                url: AppContext.baseUrl + '/hello/talk'
            }).then((data)=> {
                this.talkMessage = data.talkMessage;
            });

        }
    }
}
```

---

GoodbyeController.phpは、HelloController.phpとほとんど同じ内容です（ただしactionTalkメソッドは設けていません）。

```PHP:GoodbyeController.php
<?php

namespace App\Func\Goodbye\Controller;

use App\Func\Base\Controller\BaseController;
use Slim\App;

/**
 * Goodbyeコントローラー。
 */
class GoodbyeController extends BaseController {

    /**
     * コンストラクタ。
     * @param App $app アプリケーションオブジェクト
     */
    public function __construct(App $app) {
        parent::__construct($app);
    }

    /**
     * 表示処理。
     */
    public function actionIndex() {

        return $this->render('/Func/Goodbye/Front/View/Goodbye', []);
    }

}
```

## レイアウトHTML（Base.php）の作成

Base.phpは、前回の記事で作成したindex.htmlに代わるHTMLのテンプレートです。
ついでに、`C:\hello-vuejs\public\index.html`を削除しましょう。

```:ファイル構成
c:\hello-vuejs
    ...
    + public
        + index.html ... !!! 削除する
    + src\View\Layout
        + Base.php
```

前回の記事で、index.htmlではPOSTパラメータにアクセスすることができないと言いました。
ですが、Base.phpで動的にHTMLを生成して、リクエストパラメータ（GETやPOST）やサーバー側で任意に設定した情報を、JavaScriptコードとして生成することで、JavaScript側でPOSTパラメータやその他サーバーで設定した情報に（AppContextというグローバル変数経由で）アクセスできるようになります。

```html:Base.php
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta http-equiv="Pragma" content="no-cache">
        <meta http-equiv="Cache-Control" content="no-cache">
        <meta http-equiv="Expires" content="Mon, 26 Jul 1997 05:00:00 GMT">
        <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale = 1.0, maximum-scale = 1.0, user-scalable = no">

        <title>Hello Vue.js</title>

    </head>

    <body class="layout-base">

        <div id="app" v-cloak>
        </div>

        <?php /* グローバル関連の定義 */ ?>
        <script type="text/javascript">

            // アプリケーションコンテキストの定義（アプリケーション全体で使用したいデータ等を定義、イミュータブルなオブジェクトにする）
            var AppContext = Object.freeze({
                name: 'Hello Vue.js',
                baseUrl: '<?= $__baseUrl ?>',
                requestParams: <?= json_encode($__requestParams) ?>,
                contentsComponentPath: '<?= $__contentsViewPath ?>',
                contentsComponentId: '<?= $__contentsViewName ?>Component',
                contentsData: <?= json_encode($__contentsData) ?>
            });

        </script>

        <?php /* バンドルファイルの読み込み */ ?>
        <?php /* ※本ファイルの読み込み後に、main.jsで定義したグローバル変数などが読み込みできるようになる */ ?>
        <script src="<?= $__baseUrl . '/dist/bundle.js?' . filemtime(PUBLIC_PATH . 'dist/bundle.js') ?>" ></script>

        <?php /* Vue.js生成 */ ?>
        <script type="text/javascript">
            // Vue.js生成
            window.AppFuncs.createVuejs(AppContext.contentsComponentPath, AppContext.contentsComponentId);
        </script>

    </body>

</html>
```

## main.jsの修正

各種ファイルの新規作成に伴い、main.jsの修正が必要になります。

```:ファイル構成
c:\hello-vuejs
    ...
    + src
        + main.js
```

その前に、jQueryをインストールしたいので、`cd c:\hello-vuejs`して以下を実行しましょう。
[jQuery](https://jquery.com/)は、ajaxメソッドを利用するために導入しました。[axios](https://github.com/axios/axios)でもOKです。

```DOS:コマンドプロンプト
npm install -D jquery
```

---

main.jsを以下の内容で置き換えます。

大きく変更された箇所は、jQueryのインポート、Vue.jsインスタンスの生成メソッドをAppFuncsに移動したこと。また、最後の行でwindowオブジェクトにjQueryとAppfuncsを設定しています。

windowオブジェクトはグローバルオブジェクトなので、このようにしないと、bundle.js（main.jsが含まれている最終成果物ファイル）の外から、これらのオブジェクトを参照することができないのです。

```javascript:main.js
// -----------------------------------------------------------------------------
// ライブラリのインポート
// -----------------------------------------------------------------------------
// JQuery
import $ from 'jquery';
// VueJs
import Vue from 'vue';

/**
 * アプリケーションの関数定義
 */
const AppFuncs = {

    /**
     * Vue.jsインスタンスを生成する。
     * @param {String} componentPath コンポーネントパス
     * @param {String} componentId コンポーネントID
     * @returns {Vue} Vue.jsインスタンス
     */
    createVuejs(componentPath, componentId) {

        // 動的インポートを実施
        //
        // componentPathを以下のルールで変換
        // 例）ログインの場合
        //    /Func/Login/Front/View/Login
        //     ↓
        //   ./Func/Login/Front/View/Login.vue
        const componentLoadPromise = import("./" + componentPath.replace(/^\//, "") + ".vue");

        componentLoadPromise.then(function (value) {
            // 動的インポート完了

            // VueのComponentのグローバル登録は、Vueインスタンス生成前に実施する必要あり
            Vue.component(componentId, value.default /* import関数の戻り値に default があるので、そちらを使用する（defaultエクスポート定義を読み込むという意味になる） */);
            // Vueインスタンスの生成
            const vm = new Vue({
                el: '#app',
                render: h => h(componentId)
            });

            return vm;
        });

    }

};

// -----------------------------------------------------------------------------
// グローバル変数の定義
// -----------------------------------------------------------------------------
window.$ = $; // jQueryオブジェクト
window.AppFuncs = AppFuncs;
```

**HINT**
**ES6**で定義されたデータや関数に**ES6未満のJSコード**からアクセスする場合、ES6側で対象データをwindowオブジェクト（グローバルな領域に）に設定することで、アクセスが可能になります。

## webpack.dev.jsの修正

前回はNode.jsをWebサーバーとして利用していましたが、PHPを使用するために今回からApacheをWebサーバーとして使用することになります。ただ、Apacheを使用するとHMR（Hot Module Replacement）が機能しなくなります。そのためプロキシ設定を行います。

プロキシ設定は、webpack.dev.jsで行います。

```:ファイル構成
c:\hello-vuejs
    ...
    + webpack.dev.js
```

修正内容は以下の`devServer`部分です。`devServer`内の最終行に`proxy`を丸ごと貼り付けてください。
ついでに、`devServer`内の`openPage`を変更（index.htmlを削除したので）しましょう。

HMR（Hot Module Replacement）を利用するためには、dist/bundle.jsファイルは今まで通りNode.jsサーバーに処理させたいので、dist以下のファイルはNode.jsで、それ以外の全てのリクエストはApache Webサーバーにリクエストさせるといった事を`proxy`部分に記述しています。

```javascript:webpack.dev.js
...
module.exports = merge(common, {
    ...
    devServer: {
        ...
        // ブラウザの表示ページ
        openPage: prefixUri.substring(1) + '/hello',
        ...
        // プロキシ設定
        proxy: [
            {
                context: [
                    // すべてのリクエストを対象とする
                    '**',
                    // dist以下は対象外とする
                    '!' + prefixUri + '/dist/**'
                ],
                // Apache Webサーバーに転送する
                target: 'http://localhost:5555'
            }
        ]
    }
});
```

## 動かしてみる
処理イメージは下図の通りです。

![hello-vuejs2_index_php_flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609134/5b7d460d-cf5c-51c1-3b1d-3970641ca784.png)


`C:\hello-vuejs`で以下のコマンドを実行してください。すると、ブラウザが起動してページが表示されるはずです。

```DOS:コマンドプロンプト
npm run start
```

![hello-vuejs2_screen_hello.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609134/31e46f11-b770-ec53-0391-6e890d4f7439.png)

Helloコンポーネント上の『Hello?』ボタンを押してPHPと通信できるか試してみましょう。
すると、下図のように、『Hi, I came from space.』というメッセージが表示されましたね。

![hello-vuejs2_screen_hello_after_pressed_button.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609134/c108d526-7cda-de5c-07f6-6119bbbc8de3.png)

HMRが有効かどうかも試しましょう。Hello.jsを開いて、dataのmessageを編集してみると…。

```javascript:Hello.js
...
    data() {
        return {
            message: 'ニイハオ Vue.js',
            talkMessage: ''
        };
    },
...
```

![hello-vuejs2_screen_hello_after_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609134/5f9b1118-2dc9-23d1-d68d-b76a1909967c.png)

Goodbyeコンポーネントの画面も表示しましょう。
http://localhost:8080/hello-vuejs/goodbye

![hello-vuejs2_screen_goodbye.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609134/ccbe917a-0c53-9dde-2d8a-a488fa397c18.png)


## リリースビルドの実行

リリースビルドの実行については、フロントエンドで実行した`npm run build`と何ら変わりありません。
本番環境にリリースするファイルを作成するには、`npm run build`してbundle.jsを作成しましょう。

それと、デバッグ時はNode.jsのWebサーバーを経由してApacheにアクセスしていましたが、リリース時はApacheに直接接続するので、URLは以下のように（Apacheに直接接続）します。

http://localhost:5555/hello-vuejs/hello
http://localhost:5555/hello-vuejs/goodbye

## 最終的なファイル構成

ファイルの最終的な構成は以下の通りです。
フロントエンドとバックエンドのソースが混合された構成になっています。

```:ファイル構成
C:\hello-vuejs
    + node_modules               ... npmでインストールしたライブラリ（バージョン管理対象外とするディレクトリ）
    + public                     ... Webサーバーの公開ディレクトリ
        + .htaccess              ... Apache設定ファイル
        + index.php              ... フロントコントローラ
    + src
        + Func                   ... 機能別ディレクトリ
            + Base
                + Controller
                    + BaseController.php
            + Hello
                + Controller
                    + HelloController.php ... Helloコントローラー
                + Front\View              ... Helloコンポーネント
                    + Hello.vue
                    + Hello.html
                    + Hello.css
                    + Hello.js
            + Goodbye
                + Controller
                    + GoodbyeController.php ... Goodbyeコントローラー
                + Front\View                ... Goodbyeコンポーネント
                    + Goodbye.vue
                    + Goodbye.html
                    + Goodbye.css
                    + Goodbye.js
        + View
            + Layout
                + Base.php       ... HTMLテンプレート
        + bootstrap.php          ... PHP起動用共通処理
        + main.js                ... webpackエントリポイントファイル
    + vendor                     ... Composerでインストールしたライブラリ（バージョン管理対象外とするディレクトリ）
    + composer.json              ... Composerに関する設定ファイル
    + composer.lock              ... composer requireのスナップショットファイル
    + package.json               ... npmに関する設定ファイル
    + package-lock.json          ... npm installのスナップショットファイル
    + webpack.common.js          ... webpackの共通設定ファイル
    + webpack.dev.js             ... webpackの開発用設定ファイル
    + webpack.prod.js            ... webpackのリリース用設定ファイル
```

## バックエンドのまとめ

以上で、本記事が目標としていたWebアプリケーションのひな型が完成しました。
SPA（シングルページアプリケーション）でもない、従来のページ遷移方式で、さらにはVue.jsのSFCを用いることもできました。

webpackのビルドも、設定さえ正しくできれば開発効率を妨げる要因はなく、むしろ開発効率が向上することが実感できました。
JavaScriptのES6も慣れてくると非常に書きやすいです。本記事を応用すればTypeScriptを導入することもできます。

ただ、webpackの流儀に従うのであれば、依存するライブラリが増えた場合には、main.jsにimportを追記する必要が有ります。
画像ファイルもbundle.jsにマージしたいこともあるでしょうし、Webフォントも一緒に取り扱うという事であればwebpackの設定と深く付き合っていく必要があります。これらは、公式リファレンスを見たり、色々な記事を見て勉強していくしかありませんね。

今回作成したソースはGitHubにアップしておきました。以下のページにアクセスして確認してみてください。
[hello-vuejsソース（PHP統合版）](https://github.com/ison12/helloVuejsWithPHP)

:grinning: