簡単　STEPごとに作る自作MVC WebFramework PART1

# 自作 Php MVC Web Framework
PHPで **MVC WebFramework** を自作していきます。
LaravelやDjangoとかWebFrameworkを使っていて、
「どうやってつくられてるんやろ？？」ってなったので勉強がてら自作しようと思いました。
**パーフェクトPHP** を読んだ後に自作したので、引用されている部分もあります。
まだWebFrameworkを**自作したことない方**や、**復習がてらもう一回やろうかなと思う方**、一緒に頑張って自作していきましょう！！

この記事ではWebFrameworkをSTEPごとに作っていきます。
実際に**細かい単位でコーディング**しながら、**動作確認**ができる様になっていて、
コードが全文乗ってますので、頑張れば**初学者の方でも理解**しながら進めるかと思います。

あまりにも長くなってしまうので2部構成で記事にしていきます。
[PART2はこちら](https://qiita.com/sakunowman/items/031d135f7d2d9451c32e)

完成したレポジトリのリンクを張っておきます。
developブランチで確認してください。
[レポジトリ](https://github.com/Saknowman/study_php_web_framework)

PART1でやるSTEPは以下になります。

- [STEP0: 環境構築](#step0-%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89)
- [STEP1: 全体像](#step1-%E5%85%A8%E4%BD%93%E5%83%8F)
- [STEP2: プロジェクト](#step2-%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88)
- [STEP3: リクエストとレスポンス](#step3-%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%A8%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9)
- [STEP4: Controller](#step4-controller)
- [STEP5: View](#step5-view)
- [STEP6: Router](#step6-router)

PART2では以下です。

- [STEP7: DB](https://qiita.com/sakunowman/items/031d135f7d2d9451c32e#step7-db)
- [STEP8: セッションとユーザー認証](https://qiita.com/sakunowman/items/031d135f7d2d9451c32e#step8-%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A8%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E8%AA%8D%E8%A8%BC)
- [STEP9: Middleware](https://qiita.com/sakunowman/items/031d135f7d2d9451c32e#step9-middleware)

# STEP0: 環境構築
[LAMP仮想環境構築](https://qiita.com/sakunowman/items/637b6582db23390561cd)
上記の方法で環境を作成しました。
PHP7.4でDBはMySql8、ApacheServerということ前提で実装していきます。

ドメインは**project.com**ということで話を進めていくので、ホストPC側で仮想環境のIPアドレスにhostsでproject.comを登録することをお勧めします。
RemoteServer側では上記のLinkの方法で環境作成後に、下記を行いました。

```bash
mkdir /var/www/project.com
mkdir /var/www/project.com/public
echo "Hello World" > /var/www/project.com/public/index.html

# vhost.confの内容は後述してます。
vi /etc/httpd/conf.d/vhost.conf

service httpd restart
```

**/var/www/project.com/** がプロジェクトのディレクトリになります。
**/var/www/project.com/public**　ディレクトリが公開環境に設定します。
**/etc/httpd/conf.d/vhost.conf** を以下の内容で作成しました。
Httpdを再起動するまえに**/var/www/project.com/** と **/var/www/project.com/public** それぞれを作成してください。

```
<VirtualHost *:80>
  ServerName project.com
  DocumentRoot /var/www/project.com/public
  
  <Directory /var/www/project.com/public>
    AllowOverride All
    Require all granted
  </Directory>
  
</VirtualHost>
```




# STEP1: 全体像
とりあえず完成した時の全体像を確認しましょう。

## プロジェクト構成
プロジェクト構成は以下のようなものを想定しています。
これから実装していきますのでちらっと全体像を把握する程度で大丈夫です。

```text
project.com
      ├─ Config/
      │     ├─ ConfigApplication.php
      │     ├─ DBSettings.php
      │     ├─ DirectorySettings.php
      │     └─ ProjectSettings.php
      │
      ├─ Libs/
      │     ├─ Apps
      │     │   └─ Auth/
      │     │       ├─ Controllers/
      │     │       ├─ Entities/
      │     │       ├─ Middleware/
      │     │       ├─ migrations/
      │     │       ├─ Repositories/
      │     │       ├─ Services/
      │     │       ├─ AuthApplication.php
      │     │       └─ RoutingTable.php
      │     │
      │     ├─ Controllers/
      │     │   ├─ Controller.php
      │     │   └─ NotFoundController.php
      │     │
      │     ├─ DB/
      │     │   ├─ DBManager.php
      │     │   ├─ Entity.php
      │     │   └─ Repository.php
      │     │
      │     ├─ Https/
      │     │   ├─ Request.php
      │     │   ├─ Response.php
      │     │   ├─ Session.php
      │     │   └─ Status.php
      │     │
      │     ├─ Middleware/
      │     │   ├─ BaseMiddleware.php
      │     │   └─ MiddlewareManager.php
      │     │
      │     ├─ Routing/
      │     │   ├─ Router.php
      │     │   └─ RoutingTable.php
      │     │
      │     ├─ Utils/
      │     │   └─ AutoLoader.php
      │     │
      │     ├─ Views/
      │     │   └─ View.php
      │     │
      │     ├─ Application.php
      │     └─ Project.php
      │
      ├─ TaskApp/
      │     ├─ Controllers/
      │     ├─ Entities/
      │     ├─ Middleware/
      │     ├─ migrations/
      │     ├─ Repositories/
      │     ├─ TaskApplication.php
      │     └─ RoutingTable.php
      │
      ├─ templates/
      │     ├─ auth/
      │     │   ├─ login.tmp.php
      │     │   ├─ my_page.tmp.php
      │     │   └─ sign_up.tmp.php
      │     │
      │     └─ task/
      │         ├─ create.tmp.php
      │         ├─ detail.tmp.php
      │         ├─ edit.tmp.php
      │         └─ index.tmp.php
      │
      ├─ public/
      │     ├─ index.php
      │     └─ .htaccess
      │
      └─ bootstrap.php
```

### Applications
各Applicationにはそれぞれ**Controller**や**Entity**(DBで扱うテーブルのこととして扱います)、**Middleware**や**RoutingTable**などを実装していきます。

#### Libs/Apps
Libs/Appsは**フレームワークが提供するAPP群**です。
**標準機能**としてフレームワークが提供したいAPPを登録していく感じです。
今回は**Auth**(ユーザー認証系のアプリ)をこの中に作っていきたいと思います。

#### User Applications
**TaskApp** ディレクトリは各機能を提供するアプリケーションのひとつで、用は**TODOアプリ**を提供します。
フレームワークの使用者が**各機能ごとにAppを作成**して**Projectに登録**するような作りです。
Djangoのようにアプリケーションを作成して登録するみたいな感じで実装していこうかと思います。

### Config
**Config** ディレクトリには**各設定**に関するディレクトリです。
これ自体もProjectにAppとして登録しています。
フレームワーク自体がConfigのAppに依存しているので少し特殊です。
**DBの設定**や**Appの登録**、**Middlewareの登録**や**AppごとのRoutingの登録**などをおこないます。

### Templates
**templates** ディレクトリには実際に表示するHtmlのテンプレートが格納されています。
Templateエンジン使ったらややこしいかなと思うので、べた書きのphpでechoとかして出していくつもりです。

### Libs
**Libs** ディレクトリの中に**WebFrameworkメイン機能**を実装していこうと思います。
ここにメインの処理を実装していき、TaskAppで動作確認をしていくという流れになります。

### Public
**public** ディレクトリ内のみを利用者に公開していきます。
public以外を公開しないことで利用者にアクセスしてほしくないソースに対してのアクセス制限を書けます。
**.htaccess**で**public/index.php**に毎回アクセスするようにしていきます。
**index.php**では**bootstrap.php**を呼び出して、**Projectの起動**をしてもらいます。

### Bootstrap
**bootstrap.php** ここでリクエストが来たときのプロジェクトの設定の反映や準備を行います。
AutoLoaderを読んだりもここかなと思います。


## WebFrameWorkの処理フロー
では実際にユーザーからのリクエストがあった時の処理のながれをみていきたいと思います。

1. まず登録されているMiddlewareにリクエストを通していきます。
  ここでユーザーがログインしてないから401をかえすよーとかします。
2. 次にルーターがuriから登録されているControllerのActionを探します。
  ControllerやActionが見つからなかった場合は404を返します。
3. 見つけた場合はControllerのActionを実行します。
4. 実行した結果をレスポンスとして返します。
5. そのレスポンスをまたMiddlewareに通していきます。
  ここで何か不備があればエラーのレスポンスを返すなりします。
6. 正常に処理が進んだ場合は最後に実行結果のレスポンスをユーザーにかえします。

図で表すと以下になります。
![web_flow.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471760/e4e9fed2-033b-edcd-5dfe-b010231df543.png)


全体像の把握は以上になります。
全体像を意識しながら順番に実装していきます。


# STEP2: プロジェクト
まずは**プロジェクトの処理**や**autoloader**、**URLのリライト**などを実装していきたいです。
さらに**Configアプリを登録**していきたいと思います。

このSTEP2で実装するファイルは以下になります。

```text
project.com
      ├─ Config/
      │     ├─ ConfigApplication.php
      │     ├─ DirectorySettings.php
      │     └─ ProjectSettings.php
      │
      ├─ Libs/
      │     ├─ Utils/
      │     │   └─ AutoLoader.php
      │     │
      │     ├─ Application.php
      │     └─ Project.php
      │
      ├─ public/
      │     ├─ index.php
      │     └─ .htaccess
      │
      └─ bootstrap.php
```

## Public
まずはpublicディレクトリから見ていきましょう。
publicディレクトリのみserverに公開されています。
ここに**index.php**と**.htaccess**を設置します。

### .htaccess

```text:public/.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
```

このコードは
**「urlをみて、対象のファイルがなければ、index.phpにアクセスするようにしますよ」** という意味です
実際にpublicディレクトリにaaa.htmlなどを置いてブラウザで <http://project.com/aaa.html> とアクセスするとaaa.htmlの内容が表示されます。
そのほかのurlでアクセスするとindex.phpにアクセスします。

### index.php

```php:public/index.php
<?php

require_once '../bootstrap.php';

$project->run();
```

ここでは **bootstrap.php** をロードして、生成された **Libs/Projectクラスのインスタンスのrun関数** を実行します。

実際にbootstrap.phpとLibs/Project.phpを作成しましょう。

```php:Libs/Project.php
<?php
namespace Libs;


class Project
{
    private static Project $_instance;
    
    private function __construct()
    {
    }

    public static function instance()
    {
        if (empty(self::$_instance)) {
            self::$_instance = new Project();
        }

        return self::$_instance;
    }

    public function run()
    {
        echo "Project is running.";
    }
}
```

```php:bootstrap.php
<?php
require_once 'Libs/Project.php';
use Libs\Project;

$project = Project::instance();
```

Projectクラスは一つしか存在しないことを定義したいのでSingletonパターンで実装してます。

では実際にブラウザで確認してみましょう。 <http://project.com/>
下記の内容が表示されていれば成功です。

```text
Project is running.
```


## Autoloader
今回はComposerを使用せずに進めるので**Autoloader**もついでに作っちゃいましょう。
Autoloaderとは簡単に説明すると毎回**require**するのがめんどくさいので、自動で**require**してくれるようにする機能です。
今回の本質とは一致しないのでコードの提示だけとします。

```php:Libs/Utils/AutoLoader.php
<?php

namespace Libs\Utils;


class AutoLoader
{
    private string $system_root_dir;
    private array $applications_root_dir;

    public function __construct(string $root_dir)
    {
        $this->system_root_dir = $root_dir;
        $this->applications_root_dir = array($this->system_root_dir);
    }

    public function run()
    {
        spl_autoload_register(array($this, "loadClass"));
    }

    public function loadClass($class)
    {
        $php_file = $this->create_php_file_path($class);
        if (is_readable($php_file)) {
            require_once $php_file;
            return;
        }
    }

    private function create_php_file_path($class)
    {
        foreach ($this->applications_root_dir as $dir) {
            $pieces = array($dir);
            $class_with_name_space = ltrim($class, '\\');
            $pieces = array_merge($pieces, explode('\\', $class_with_name_space));
            $result = implode(DIRECTORY_SEPARATOR, $pieces) . ".php";
            if (is_readable($result)) return $result;
        }
        return null;
    }
}
```

AutoLoaderクラスに**プロジェクトのルートディレクトリ**を渡してインスタンを生成し、実行します。

ルートディレクトリを指定したいので**ConfigディレクトリにDirectorySetting.php**を作成していきます。

```php:Config/DirectorySettings.php
<?php
namespace Config;


class DirectorySettings
{
    public const APPLICATION_ROOT_DIR = __DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR;
}
```

ではbootstrap.phpでAutoLoaderを実行するようにしましょう。

```php
<?php
require_once 'Config/DirectorySettings.php';
require_once 'Libs/Utils/AutoLoader.php';

use Config\DirectorySettings;
use Libs\Utils\AutoLoader;


$auto_loader = new AutoLoader(DirectorySettings::APPLICATION_ROOT_DIR);
$auto_loader->run();

$project = \Libs\Project::instance();
```

Libs/Projectが自動でRequireされるようになりました。
ブラウザで確認して動作に問題がなければ成功です。<http://project.com/>

## Config
次はConfigアプリを実装していきましょう。

### Application.php
まずはLibs/Application.phpを作成します。
これは**各Appごとを登録**する際に使用します。
Appを起動する際に**Applicationクラスのready**を実行していくという仕様になります。

```php:Libs/Application.php
<?php
namespace Libs;


class Application
{
    public function ready(){
        // Override this method in subclasses to run code when Project starts.
    }
}
```

では実際に**Applicationクラスを継承したConfigApplication**を実装しましょう。

```php:Config/ConfigApplication.php
<?php
namespace Config;


use Libs\Application;

class ConfigApplication extends Application
{
    public function ready()
    {
        echo "Config application is ready <br>";
    }
}
```

これでConfigApplicationのreadyが実行されると
**Config application is ready** と表示されるます。

### ProjectSettings.php
プロジェクトの設定をする場所が必要になるので作成します。
ここに先ほど作成した**ConfigApplicationを登録**しましょう。

```php:Config/ProjectSettings.php
<?php
namespace Config;


class ProjectSettings
{
    public const IS_DEBUG = true;
    public const APPLICATIONS = [
        ConfigApplication::class,
    ];
}
```

### Bootstrap
ではbootstrap.phpで以下のことをやっていきましょう。
- Appの登録と初期化
- DEBUGモードならエラーがあった時に表示するようにする。

```php:bootstrap.php
<?php
require_once 'Config/ProjectSettings.php';
require_once 'Config/DirectorySettings.php';
require_once 'Libs/Utils/AutoLoader.php';

use Config\ProjectSettings;
use Config\DirectorySettings;
use Libs\Utils\AutoLoader;

if (ProjectSettings::IS_DEBUG)
    ini_set('display_errors', 'On');

$auto_loader = new AutoLoader(DirectorySettings::APPLICATION_ROOT_DIR);
$auto_loader->run();

foreach (ProjectSettings::APPLICATIONS as $APPLICATION){
    $application = new $APPLICATION();
    $application->ready();
}

$project = \Libs\Project::instance();
```

では実際にブラウザで確認してみましょう。 <http://project.com/>
下記の内容が表示されていれば成功です。

```text
Config application is ready
Project is running.
```


# STEP3: リクエストとレスポンス
次はリクエストとレスポンスの実装を行っていきます。

このSTEP3で実装・編集するファイルは以下になります。

```text
project.com
      └── Libs/
            ├─ Https/
            │   ├─ Request.php
            │   ├─ Response.php
            │   └─ Status.php
            │
            └─ Project.php
      
```

## レスポンス
ではまず、**レスポンス用のクラス**を作成し動作の確認までを行いましょう。

### Libs/Https/Status.php
まず最初に**Libs/Https/Status.php**を作成します
これはHttpのステータスコードやメッセージを扱うクラスです。
DjangoRestFrameworkに列挙されているステータスを格納しています。
ただ列挙しているだけなのでコピペで問題ないです。

```php:Libs/Https/Status.php
<?php
namespace Libs\Https;


class Status
{
    public static function text($status_code)
    {
        return empty(self::TEXT[$status_code]) ? "" : self::TEXT[$status_code];
    }

    # Success
    public const HTTP_200_OK = 200;
    public const HTTP_201_CREATED = 201;
    public const HTTP_202_ACCEPTED = 202;
    public const HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203;
    public const HTTP_204_NO_CONTENT = 204;
    public const HTTP_205_RESET_CONTENT = 205;
    public const HTTP_206_PARTIAL_CONTENT = 206;
    public const HTTP_207_MULTI_STATUS = 207;
    public const HTTP_208_ALREADY_REPORTED = 208;
    public const HTTP_226_IM_USED = 226;

    # Redirect
    public const HTTP_300_MULTIPLE_CHOICES = 300;
    public const HTTP_301_MOVED_PERMANENTLY = 301;
    public const HTTP_302_FOUND = 302;
    public const HTTP_303_SEE_OTHER = 303;
    public const HTTP_304_NOT_MODIFIED = 304;
    public const HTTP_305_USE_PROXY = 305;
    public const HTTP_306_RESERVED = 306;
    public const HTTP_307_TEMPORARY_REDIRECT = 307;
    public const HTTP_308_PERMANENT_REDIRECT = 308;

    # Client Error
    public const HTTP_400_BAD_REQUEST = 400;
    public const HTTP_401_UNAUTHORIZED = 401;
    public const HTTP_402_PAYMENT_REQUIRED = 402;
    public const HTTP_403_FORBIDDEN = 403;
    public const HTTP_404_NOT_FOUND = 404;
    public const HTTP_405_METHOD_NOT_ALLOWED = 405;
    public const HTTP_406_NOT_ACCEPTABLE = 406;
    public const HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407;
    public const HTTP_408_REQUEST_TIMEOUT = 408;
    public const HTTP_409_CONFLICT = 409;
    public const HTTP_410_GONE = 410;
    public const HTTP_411_LENGTH_REQUIRED = 411;
    public const HTTP_412_PRECONDITION_FAILED = 412;
    public const HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413;
    public const HTTP_414_REQUEST_URI_TOO_LONG = 414;
    public const HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415;
    public const HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
    public const HTTP_417_EXPECTATION_FAILED = 417;
    public const HTTP_422_UNPROCESSABLE_ENTITY = 422;
    public const HTTP_423_LOCKED = 423;
    public const HTTP_424_FAILED_DEPENDENCY = 424;
    public const HTTP_426_UPGRADE_REQUIRED = 426;
    public const HTTP_428_PRECONDITION_REQUIRED = 428;
    public const HTTP_429_TOO_MANY_REQUESTS = 429;
    public const HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
    public const HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451;

    # Server Error
    public const HTTP_500_INTERNAL_SERVER_ERROR = 500;
    public const HTTP_501_NOT_IMPLEMENTED = 501;
    public const HTTP_502_BAD_GATEWAY = 502;
    public const HTTP_503_SERVICE_UNAVAILABLE = 503;
    public const HTTP_504_GATEWAY_TIMEOUT = 504;
    public const HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505;
    public const HTTP_506_VARIANT_ALSO_NEGOTIATES = 506;
    public const HTTP_507_INSUFFICIENT_STORAGE = 507;
    public const HTTP_508_LOOP_DETECTED = 508;
    public const HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509;
    public const HTTP_510_NOT_EXTENDED = 510;
    public const HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511;

    public const TEXT = [
        200 => 'Ok',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi Status',
        208 => 'Already Reported',
        226 => 'Im Used',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => 'Reserved',
        307 => 'Temporary Redirect',
        308 => 'Permanent Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request Uri Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        422 => 'Unprocessable Entity',
        423 => 'Locked',
        424 => 'Failed Dependency',
        426 => 'Upgrade Required',
        428 => 'Precondition Required',
        429 => 'Too Many Requests',
        431 => 'Request Header Fields Too Large',
        451 => 'Unavailable For Legal Reasons',
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'Http Version Not Supported',
        506 => 'Variant Also Negotiates',
        507 => 'Insufficient Storage',
        508 => 'Loop Detected',
        509 => 'Bandwidth Limit Exceeded',
        510 => 'Not Extended',
        511 => 'Network Authentication Required',
    ];
}

```

### Libs/Https/Response.php
次に**Libs/Https/Response.php**を作成します。

このクラスは、実際に**Responseを返す**際に利用します。
Responseに**Headerをセット**したり、**結果の出力**を行ったりします。
**Redirectの機能**もここに実装します。

```php:Libs/Https/Response.php
<?php
namespace Libs\Https;


class Response
{
    private string $content;
    private int $status_code;
    private string $status_text;
    private array $http_headers;

    /**
     * Response constructor.
     * @param string $content
     * @param int $status_code
     * @param string $status_text
     */
    public function __construct(
        string $content = "",
        int $status_code = Status::HTTP_200_OK,
        string $status_text = '')
    {
        $this->content = $content;
        $this->status_code = $status_code;
        $this->status_text = empty($status_text) ? Status::text($status_code) : $status_text;
        $this->http_headers = array();
    }

    /**
     * Send response with header and content.
     */
    public function send()
    {
        header('HTTP/1.1 ' . $this->status_code . ' ' . $this->status_text);

        foreach ($this->http_headers as $name => $value) {
            header($name . ': ' . $value);
        }

        echo $this->content;
    }

    public function setHttpHeaders($name, $value)
    {
        $this->http_headers[$name] = $value;
    }

    public function statusCode()
    {
        return $this->status_code;
    }

    public function statusText()
    {
        return $this->status_text;
    }

    public static function redirect($location){
        $response = new self("",
            Status::HTTP_301_MOVED_PERMANENTLY);
        $response->setHttpHeaders('Location', $location);
        return $response;
    }
}
```

Responseクラスの引数に**コンテント、ステータスコード**を渡し、send関数で出力します。
Headerを別途追加したいときはsetHttpHeaders関数で追加します。
ではProject.phpを編集し、実際に確認してみましょう。

```php:Libs/Project.php
...

class Project
{
    ...

    public function run()
    {
        $response = new \Libs\Https\Response('This is content of response.');
        $response->send();
    }
}
```

ついでにConfig/ConfigApplication.phpのecho文が邪魔なので消してしまいましょう。

```php:Config/ConfigApplication.php
<?php
...

class ConfigApplication extends Application
{
    public function ready()
    {
    }
}
```


デベロッパーツール(ChromeならF12)で**ResponseのHeader**も確認しましょう。
では実際にブラウザで確認してみましょう。 <http://project.com/>
**HttpのStatusが200**になっていて、下記の内容が表示されていれば成功です。

```
This is content of response.
```

ResponseのStatusを変更したりHeaderを追加してみたりして確認してみましょう。

```php
...

use Libs\Https\Status;

class Project
{
    ...
    
    public function run()
    {
        $response = new \Libs\Https\Response('This is content of response.', \Libs\Https\Status::HTTP_404_NOT_FOUND);
        $response->setHttpHeaders('ADD_NEW_HEADER', "ADDED");
        $response->send();
    }
}
```

**HttpのStatusが404**になっていて。
**Response Header**に**ADD_NEW_HEADER: ADDED**が追加されていれば成功です。


## リクエスト
次に、**リクエスト用のクラス**を作成し動作の確認までを行いましょう。

### Libs/Https/Request.php
Libs/Https/Request.phpを実装します。

このクラスは以下の機能を持っています。
- Httpメソッドタイプの取得 (POST / GET / PUT / ...)
- GETやPOSTのデータを取得
- HttpHeaderの取得
- ホスト部分の取得 (project.com/aaa/bbb => project.com)
- ドメイン以降のパスの取得 (project.com/aaa/bbb => aaa/bbb)

```php:Libs/Https/Request.php
<?php
namespace Libs\Https;


class Request
{
    private static Request $instance;
    private array $headers;

    private function __construct()
    {
        $this->headers = getallheaders();
    }

    public static function instance(): Request
    {
        if (empty(self::$instance)) {
            self::$instance = new Request();
        }

        return self::$instance;
    }

    public function methodType(): string
    {
        if (is_null($this->post('_method')))
            return $_SERVER['REQUEST_METHOD'];
        return $this->post('_method');
    }

    public function get(string $name, $default = null)
    {
        if (isset($_GET[$name]))
            return $_GET[$name];
        return $default;
    }

    public function post($name, $default = null)
    {
        if (isset($_POST[$name]))
            return $_POST[$name];
        return $default;
    }

    /**
     * @param null $name
     * @return array | string
     */
    public function header($name = null)
    {
        if (empty($name))
            return getallheaders();
        return empty($this->headers[$name]) ? '' : $this->headers[$name];
    }

    public function host(): string
    {
        if (!empty($_SERVER['HTTP_HOST']))
            return $_SERVER['HTTP_HOST'];
        return $_SERVER['SERVER_NAME'];
    }

    public function requestUri(): string
    {
        return $_SERVER['REQUEST_URI'];
    }

    public function baseUrl(): string
    {
        $script_name = $_SERVER['SCRIPT_NAME'];
        $request_uri = $this->requestUri();

        if (0 === strpos($request_uri, $script_name))
            return $script_name;
        else if (0 === strpos($request_uri, dirname($script_name)))
            return rtrim(dirname($script_name));

        return '';
    }

    public function pathInfo(): string
    {
        $base_url = $this->baseUrl();
        $request_uri = $this->requestUri();

        $pos = strpos($request_uri, '?');
        if (false !== $pos)
            $request_uri = substr($request_uri, 0, $pos);

        $path_info = (string)substr($request_uri, strlen($base_url));

        return $path_info;
    }

}

```

では動作確認を行うためにProject.phpを編集します。

```php:Libs/Project.php
<?php
namespace Libs;


use Libs\Https\Request;
use Libs\Https\Response;


class Project
{
    private static Project $_instance;
    private Request $_request;

    private function __construct()
    {
        $this->_request = Request::instance();
    }

    public static function instance()
    {
        if (empty(self::$_instance)) {
            self::$_instance = new Project();
        }

        return self::$_instance;
    }

    public function run()
    {
        $content = '';
        $content .= 'Method type:' . $this->_request->methodType() . '<br>';
        $content .= 'Header Connection:' . $this->_request->header('Connection') . '<br>';
        $content .= 'Host :' . $this->_request->host() . '<br>';
        $content .= 'Request uri:' . $this->_request->requestUri() . '<br>';
        $content .= 'Path info:' . $this->_request->pathInfo() . '<br>';
        $content .= 'GET name:' . $this->_request->get('name') . '<br>';
        $content .= 'GET aaa:' . $this->_request->get('aaa') . '<br>';
        $response = new Response($content);
        $response->send();
    }

}
```




では実際にブラウザで確認してみましょう。 <http://project.com/aaa/bbb?name=John&aaa=AAA_dao>
下記の内容が表示されていれば成功です。

```text
Method type:GET
Header Connection:keep-alive
Host :project.com
Request uri:/aaa/bbb?name=John&aaa=AAA_dao
Path info:aaa/bbb
GET name:John
GET aaa:AAA_dao
```


# STEP4: Controller
次は**コントローラーの実装**を行っていきます。

このSTEP4で実装・編集するファイルは以下になります。

```text
project.com
      ├─ Config/
      │     └─ ProjectSettings.php
      │
      ├─ Libs/
      │     ├─ Controllers/
      │     │   ├─ Controller.php
      │     │   └─ NotFoundController.php
      │     │
      │     └─ Project.php
      │
      └─ TaskApp/
            ├─ Controllers/
            │   └─ TasksController.php
            │
            └─ TaskApplication.php
```

## Libs/Controllers/
まずはLibs/Controllersディレクトリに **Controller.php** と **NotFoundController.php** を作成していきます。

### Libs/Controllers/Controller.php
このクラスは以下の機能を持っています。
- アクションの実行
- リダイレクト
- 404を返す機能 (後述する、NotFoundControllerのindexを返します)

```php:Libs/Controllers/Controller.php
<?php
namespace Libs\Controllers;


use Config\ProjectSettings;
use Libs\Https\Request;
use Libs\Https\Response;

class Controller
{
    /**
     * @var Request
     */
    protected Request $_request;

    public function __construct()
    {
        $this->_request = Request::instance();
    }

    public function run($action, $params = [])
    {
        if (!method_exists($this, $action)) {
            return $this->render404("Page not found.");
        }

        return $this->$action($params);
    }

    protected function redirect($uri)
    {
        return Response::redirect($uri);
    }

    protected function render404($message='Page not found.')
    {
        $controller = ProjectSettings::NOT_FOUND_CONTROLLER;
        $controller = new $controller($message);
        return $controller->index([]);
    }
}
```

### Libs/Controllers/NotFoundController.php
このクラスは404のResponseを生成する機能を持っています。

```php:Libs/Controllers/NotFoundController.php
<?php
namespace Libs\Controllers;


use Libs\Https\Response;
use Libs\Https\Status;

class NotFoundController extends Controller
{
    private string $message;

    public function __construct($message='Page not found.')
    {
        parent::__construct();
        $this->message = $message;
    }

    public function index($params)
    {
        return new Response($this->message, Status::HTTP_404_NOT_FOUND);
    }

}
```

NotFoundControllerクラスを**ProjectSettings::NOT_FOUND_CONTROLLER** に登録します。

```php:Config/ProjectSettings.php
<?php
namespace Config;

use Libs\Controllers\NotFoundController;

class ProjectSettings
{
    public const IS_DEBUG = true;

    public const APPLICATIONS = [
        ConfigApplication::class,
    ];

    public const NOT_FOUND_CONTROLLER = NotFoundController::class;
}
```

## TaskApp
コントローラの実装が完了しました。
先ほど実装したコントローラーを使って、**TaskAppのコントローラーを作成** して **APPを登録** して動作確認をしましょう。

### TaskApp/TaskApplication.php
とりあえずTaskAppを登録するためにTaskApplication.phpを作成します。

```php:TaskApp/TaskApplication.php
<?php
namespace TaskApp;


use Libs\Application;

class TaskApplication extends Application
{

}
```

作成したTaskApplicationを登録します。

```php:Config/ProjectSettings.php
<?php
namespace Config;

use Libs\Controllers\NotFoundController;
use TaskApp\TaskApplication;

class ProjectSettings
{
    public const IS_DEBUG = true;

    public const APPLICATIONS = [
        ConfigApplication::class,
        TaskApplication::class,
    ];

    public const NOT_FOUND_CONTROLLER = NotFoundController::class;
}
```

### TaskApp/Controllers/TasksController.php
では実際に動作するコントローラーを作成しましょう。

```php:TaskApp/Controllers/TasksController.php
<?php
namespace TaskApp\Controllers;


use Libs\Controllers\Controller;
use Libs\Https\Response;

class TasksController extends Controller
{
    public function index($params)
    {
        return new Response("This is index of task controller.");
    }

    public function detail($params)
    {
        return new Response("This is detail of task controller. <br> id: " . $params['id']);

    }
}
```

では、Project.phpを編集して、動作確認を行います。

```php:Libs/Project.php
<?php
namespace Libs;


use Libs\Controllers\Controller;
use Libs\Https\Request;
use Libs\Https\Response;
use Libs\Https\Status;
use TaskApp\Controllers\TasksController;

class Project
{
    private static Project $_instance;
    private Request $_request;

    private function __construct()
    {
        $this->_request = Request::instance();
    }

    public static function instance()
    {
        if (empty(self::$_instance)) {
            self::$_instance = new Project();
        }

        return self::$_instance;
    }

    public function run()
    {
        list($controller, $action, $params) = $this->_selectController();
        $response = $this->_actionController($controller, $action, $params);
        $response->send();
    }

    private function _selectController()
    {
        $controller = new TasksController();
        $action = 'index';
        $params = [];
        return [$controller, $action, $params];
    }

    private function _actionController(Controller $controller, string $action, array $params)
    {
        return $controller->run($action, $params);
    }
}
```

以下の関数が追加されました。
- _selectController: コントローラー、アクション、パラムを選択する関数
- _actionController: コントローラのアクションを実行する関数

流れとしては、run関数内で　
1. _selectControllerを実行しコントローラーとアクションを選択し
2. その情報をもとに _actionController関数で実行し
3. そのレスポンスを返すという流れになります。

今回の場合は**TaskControllerのindexアクション**が実行されます。
では実際にブラウザで確認してみましょう。 <http://project.com>
下記の内容が表示されていれば成功です。

```text
This is index of task controller.
```

アクションを**detail**に変更し動作確認をしてみましょう。

```php:Libs/Project.php
class Project
{
    ...
    
    private function _selectController()
    {
        $controller = new TasksController();
        $action = 'detail';
        $params = ['id' => 1];
        return [$controller, $action, $params];
    }

    ...
}
```

上記の変更により**TaskControllerのdetailアクション**が実行されます。
では実際にブラウザで確認してみましょう。 <http://project.com>
下記の内容が表示されていれば成功です。

```text
This is detail of task controller.
id: 1
```




# STEP5: View
次はViewの実装を行っていきます。
**Templateファイル**を読み込んで**Responseとして返す**という処理を行います。
Templateファイル側ではechoなどを駆使して動的に生成を行っていきます。
ようはめちゃくちゃはしょったテンプレートシステムです。

このSTEP5で実装・編集するファイルは以下になります。


```text
project.com
      ├─ Config/
      │     └─ DirectorySettings.php
      │
      ├─ Libs/
      │     ├─ Controllers/
      │     │   └─ Controller.php
      │     │
      │     ├─ Views/
      │     │   └─ View.php
      │     │
      │     └─ Project.php
      │
      ├─ TaskApp/
      │     └─ Controllers/
      │
      └─ templates/
            └─ task/
                ├─ detail.tmp.php
                └─ index.tmp.php
```

## Config/DirectorySettings.php
まずは**テンプレートのルートディレクトリ**がどこか設定しましょう。

```php:Config/DirectorySettings.php
<?php
namespace Config;


class DirectorySettings
{
    public const APPLICATION_ROOT_DIR = __DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR;
    public const TEMPLATES_ROOT_DIR = self::APPLICATION_ROOT_DIR . DIRECTORY_SEPARATOR . "templates" . DIRECTORY_SEPARATOR;
}
```

## Libs/Views/View.php
では、テンプレートエンジン（笑）のかなめになる **Libs/Views/View.php** を実装していきます。


このクラスは以下の機能を持っています。
- テンプレートファイルを読み込んで、実行し、コンテンツを生成する。
- テンプレートファイル内でエスケープ機能を利用可能にする。


```php:Libs/Views/View.php
<?php
namespace Libs\Views;


use Config\DirectorySettings;

class View
{
    protected array $_defaultData = [];

    public function __construct()
    {
        $this->_defaultData['escape'] = $this->escape();
    }

    public function render($_file_path_after_templates_dir, $_data = array())
    {
        $_file = DirectorySettings::TEMPLATES_ROOT_DIR
            . $_file_path_after_templates_dir . '.tmp.php';

        extract(array_merge($this->_defaultData, $_data));

        // echoとかの出力をため込む宣言です。
        ob_start();
        // ため込み先のバッファの上限を無効化します。
        ob_implicit_flush(0);
        require $_file;
        // ため込んだ出力を$contentに代入します。
        $content = ob_get_clean();

        return $content;
    }

    public function escape()
    {
        return function ($string, $echo = true) {
            $value = htmlspecialchars($string, ENT_QUOTES < 'UTF-8');
            if (!$echo)
                return $value;
            echo $value;
        };
    }
}
```

 [extractはここを参照してください](https://www.php.net/manual/ja/function.extract.php)。説明できません。 
 ob_start() ~ ob_get_clean()までの意味は[ここを参考](https://se-tomo.com/2019/01/02/%E3%80%90php%E3%80%91%E5%87%BA%E5%8A%9B%E3%81%AE%E3%82%BF%E3%82%A4%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%92%E5%88%B6%E5%BE%A1%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/)にしてください
 
 ## Libs/Controllers/Controller.php
 **Controllerクラスにrender関数**を追加します。
 **テンプレートファイルを指定**して、**データを渡して**、**動的にコンテンツを生成**した結果をレスポンスとして返すための関数です。
 
 ```php:Libs/Controllers/Controller.php
 <?php
namespace Libs\Controllers;


use Config\ProjectSettings;
use Libs\Https\Request;
use Libs\Https\Response;
use Libs\Views\View;

class Controller
{
    protected Request $_request;

    public function __construct()
    {
        $this->_request = Request::instance();
    }

    public function run($action, $params = [])
    {
        if (!method_exists($this, $action)) {
            return $this->render404("Page not found.");
        }

        return $this->$action($params);
    }

    protected function render($file_path_after_templates_dir, $data=[])
    {
        $view = new View();
        return new Response($view->render($file_path_after_templates_dir, $data));
    }

    protected function redirect($uri)
    {
        return Response::redirect($uri);
    }

    protected function render404($message='Page not found.')
    {
        $controller = ProjectSettings::NOT_FOUND_CONTROLLER;
        $controller = new $controller($message);
        return $controller->index([]);
    }
}
 ```
 
## TaskApp
では実際にTaskAppで動作確認を行いましょう。
まず、**コントローラでrender関数を使用**するように変更しましょう。

```php:TaskApp/Controllers/TasksController.php
...

class TasksController extends Controller
{
    public function index($params)
    {
        return $this->render('tasks/index');
    }

    public function detail($params)
    {
        $data = ['title' => 'Create web framework.', 'status' => 'DOING'];
        return $this->render('tasks/detail', $data);
    }
}
```

render関数の第一引数には**templateディレクトリ以下の.tmp.php拡張子のテンプレートファイル**を指定します。
例:
- tasks/index => templates/tasks/index.tmp.php
- aaa/bbb/sample => templates/aaa/bbb/sample.tmp.php

### templates/tasks/index.tmp.php
では実際にテンプレートファイルを用意します。
拡張子がtmp.phpのものをテンプレートファイルとして扱うように設定されています。
なので**templates/tasks/index.tmp.php**を作成しましょう。

```php:templates/tasks/index.tmp.php
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<h1>Tasks</h1>
This is tasks index page from template.
</body>
</html>
```

では実際にProjectを編集して動作確認を行いましょう。

```php:Libs/Project.php
<?php

class Project
{
    ...
    
    private function _selectController()
    {
        $controller = new TasksController();
        $action = 'index';
        return [$controller, $action, []];
    }

    ...
}

```

これでテンプレートファイルからコンテントが生成されているはずです。
では実際にブラウザで確認してみましょう。 <http://project.com>
下記の内容が表示されていれば成功です。

```text
Tasks
This is tasks index page from template.
```


### templates/tasks/detail.tmp.php
次にデータの出力確認のためにdetail.tmp.phpを作成していきます。

TasksControllerでdetailテンプレートには下記のデータが渡されています。
- 'title' => 'Create web framework'
- 'status' => 'DOING'

これらのデータを出力するには**Viewクラスで提供されている$escape関数**を使用します。
動的な出力には基本的にエスケープ処理が必要です。
エスケープ処理をしないことで任意のJSを実行されたりなどのクラッキングの対象になります。
下記のファイルを参考にいろいろ試してみてください。

```
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<h2>Tasks Detail Page from Template.</h2>

<ul>
    <li>
        Title: <?php $escape($title) ?>
    </li>
    <li>
        Status: <?php $escape($status) ?>
    </li>
</ul>
</body>
</html>
```

では実際にProjectを編集して動作確認を行いましょう。

```php:Libs/Project.php
<?php

class Project
{
    ...
    
    private function _selectController()
    {
        $controller = new TasksController();
        $action = 'detail';
        return [$controller, $action, []];
    }

    ...
}

```

これでテンプレートファイルに**データが組み込まれたコンテント**が生成されているはずです。
では実際にブラウザで確認してみましょう。 <http://project.com>
下記の内容が表示されていれば成功です。

```text
Tasks Detail Page from Template.
・　Title: Create web framework.
・　Status: DOING
```


# STEP6: Router
次は**Routerの実装**を行っていきます。
Routerは**URLによってコントローラとアクションを選択する機能**を担当します。
例:
- project.com/tasks => TaskController::index()
- project.com/tasks/1 => TaskController::detail()

このFrameworkでは下記の手順でルートを登録していきます。
- 各APPごとにルーティングテーブルを用意する。
- 各APPごとのルーティングテーブルをProjectのルーターに登録する。

プロジェクトの実行時にプロジェクトのルーターからコントローラーとアクションを選択して、実行する流れになります。

自分の実装がややこしくなってしまって、いままでより複雑になりますがご了承ください。

このSTEP6で実装・編集するファイルは以下になります。

```text
project.com
      ├─ Config/
      │     └─ ProjectSettings.php
      │
      ├─ Libs/
      │     ├─ Routing/
      │     │   ├─ Router.php
      │     │   └─ RoutingTable.php
      │     │
      │     └─ Project.php
      │
      ├─ TaskApp/
      │     ├─ Controllers/
      │     │   └─ TasksController.php
      │     │
      │     └─ RoutingTable.php
      │
      └─ templates/
            └─ task/
                ├─ detail.tmp.php
                └─ index.tmp.php
```


## Libs/Routing/RoutingTable.php
各APPごとで設定するための**ルーティングテーブルクラス**を実装していきます。
このクラスは以下の機能を持っています。
- ルーティング情報の登録
- $urlPatternsに登録されているルーティング情報の一括登録
- パスとメソッドタイプよって登録されているコントローラーとアクションの取得

パスとはドメイン以下のurlのことと定義します（project.com/aaa/bbb => aaa/bbb）
登録と取得の実装方法としては**木構造**を使用しています。
パスの区切り( / )ごとに枝をはやしていって、葉っぱにコントローラとアクションが書いてある感じです。
実際の登録方法は後述します。



```php:Libs/Routing/RoutingTable.php
<?php
namespace Libs\Routing;


class RoutingTable
{
    protected array $urlPatterns = [];
    private array $tables = [];

    /**
     * Register all $this->urlPatterns if table is empty.
     */
    public function registerMyUrlPatterns()
    {
        if (count($this->tables) > 0)
            return;

        foreach ($this->urlPatterns as $urlPattern) {
            $this->register(
                $urlPattern[0], $urlPattern[1], $urlPattern[2],
                empty($urlPattern[3]) ? 'index' : $urlPattern[3]);
        }
    }

    public function register($pattern, $methodType, $class, $action = 'index')
    {
        if (empty($this->tables[$methodType])) {
            $this->tables[$methodType] = [];
        }
        $pieces = explode('/', $pattern);
        $current_pointer = &$this->tables[$methodType];
        foreach ($pieces as $piece) {
            if (empty($current_pointer[$piece])) {
                $current_pointer[$piece] = [];
            }
            $current_pointer = &$current_pointer[$piece];
        }
        $current_pointer = [
            'class' => $class,
            'action' => $action
        ];
    }

    /**
     * Resolve controller information like this:
     *
     *   return [
     *    'class' => SomeController::class,
     *    'action' => 'index',
     *    'params' => ['id' => 1]
     *   ]
     *
     * Return null if failed to resolve.
     *
     * @param $pathInfo
     * @param $methodType
     * @return array|null
     */
    public function resolve($pathInfo, $methodType)
    {
        if (empty($this->tables[$methodType]))
            return null;

        $params = [];
        $branch = $this->tables[$methodType];
        $pieces = explode('/', $pathInfo);
        for($i = 0; $i < count($pieces); $i++){
            $result = $this->_pickBranch($branch, $pieces[$i], $params);
            if (is_null($result))
                return null;
            $branch = $result;
        }
        if (empty($result['class']) || empty($result['action']))
            return null;
        return ['class' => $result['class'], 'action' => $result['action'], 'params' => $params];
    }

    /**
     * Return null if not found.
     *
     * @param $branch
     * @param $piece
     * @param $params
     * @return mixed|null
     */
    private function _pickBranch($branch, $piece, &$params)
    {
        if (empty($branch[$piece])) {
            return null;
        }
        $result = $branch[$piece];
        return $result;
    }
}
```

## TaskApp/RoutingTable.php
実際に例を見たほうがはやいので、TaskAppのRoutingTableを作成してみましょう。

```php:TaskApp/RoutingTable.php
<?php
namespace TaskApp;


use TaskApp\Controllers\TasksController;

class RoutingTable extends \Libs\Routing\RoutingTable
{
    protected array $urlPatterns = [
        ['', 'GET', TasksController::class, 'index'],
        ['detail', 'GET', TasksController::class, 'detail'],
    ];
}
```

$urlPatternsに設定しておいて後で**一括で登録**するという方法をとっています。
$urlPatternsには以下のように情報を登録します。

```php
protected array $urlPatterns = [
  // ['パス', 'メソッド', コントローラのクラス, 'アクション名'],
  ['detail', 'GET', TasksController::class, 'detail'],
]
```

## Libs/Project.php
では実際にProject.phpを編集して動作確認を行いましょう

```php:Libs/Project.php
<?php
...

class Project
{
    ...
    
    private function _selectController()
    {
        $routing_table = new \TaskApp\RoutingTable();
        $routing_table->registerMyUrlPatterns();
        $result = $routing_table->resolve($this->_request->pathInfo(), $this->_request->methodType());

        if (is_null($result)){
            $controller = \Config\ProjectSettings::NOT_FOUND_CONTROLLER;
            return [new $controller(), 'index', []];
        }

        return [new $result['class'], $result['action'], $result['params']];
    }
    
    ...
}

```

では実際にブラウザで確認してみましょう。 <http://project.com>
下記の内容が表示されていれば成功です。

```text
Tasks
This is tasks index page from template.
```

ではパスをdetailに変えてブラウザで確認してみましょう。 <http://project.com/detail>
下記の内容が表示されていれば成功です。

```text
Tasks Detail Page from Template.
・　Title: Create web framework.
・　Status: DOING
```

次にパスを登録されていないパスに変えてブラウザで確認してみましょう。 <http://project.com/ajfij>
下記の内容が表示されていれば成功です。

```text
Page not found.
```


## Libs/Routing/Router.php
RoutingTableの動作確認はできましたね。
しかしこれでは**TaskAppだけしか使えません**。
なので**APPごとのRoutingTableをルーターに登録**するようにしましょう。

```php:Libs/Routing/Router.php
<?php
namespace Libs\Routing;


use Libs\Https\Request;

class Router
{
    private array $routingTables = [];

    public function __construct($routingTableClasses=[])
    {
        foreach ($routingTableClasses as $routingTableClass){
            $this->add($routingTableClass[0], new $routingTableClass[1]);
        }
    }

    public function add($prefixPregPattern, RoutingTable $routingTable)
    {
        $routingTable->registerMyUrlPatterns();
        $this->routingTables[] = [
            'prefixPregPattern' => $prefixPregPattern
            , 'table' => $routingTable];
    }

    public function resolve(Request $request){
        $path_info = $request->pathInfo();
        $result = null;
        foreach ($this->routingTables as $routingTable){
            if (preg_match($routingTable['prefixPregPattern'], $path_info, $matches)){
                $current_path_info = substr($path_info, strlen($matches[0]));
                $result = $routingTable['table']->resolve($current_path_info, $request->methodType());
                break;
            }
        }

        return $result;
    }
}
```

先ほどのRoutingTableをこのクラスに登録していきます。

**ProjectSettingsで登録するRoutingTableの設定**を行います。

```php:Config/ProjectSettings.php
<?php
namespace Config;


use Libs\Controllers\NotFoundController;
use TaskApp\TaskApplication;

class ProjectSettings
{
    public const IS_DEBUG = true;
    public const APPLICATIONS = [
        ConfigApplication::class,
        TaskApplication::class
    ];

    public const ROUTING_TABLE_CLASSES = [
        ['/^tasks(\/|)/', \TaskApp\RoutingTable::class],
    ];


    public const NOT_FOUND_CONTROLLER = NotFoundController::class;
}
```

**RoutingTableをROUTING_TABLE_CLASSES**に登録していきます。
/^tasks(\/|)/は正規表現でtasksかtasks/ということになります。
URLがこのパターンにマッチしたときに登録されている**RoutingTableのresolve関数**を使用することになります。
例：
**project.com/tasks/detail** にアクセスした場合、正規表現のパターンに一致しているので
TaskApp/RoutingTableのresolve関数の引数に'detail'とメソッドタイプを渡して実行します。
ちょっとややこしいので動作確認を行いましょう。

## Project.php
project.phpを編集して動作確認を行います。

```php:Libs/Project.php
<?php
namespace Libs;


use Config\ProjectSettings;
use Libs\Controllers\Controller;
use Libs\Https\Request;
use Libs\Routing\Router;

class Project
{
    private static Project $_instance;
    private Request $_request;
    private Router $_router;

    private function __construct()
    {
        $this->_request = Request::instance();
        $this->_router = new Router(ProjectSettings::ROUTING_TABLE_CLASSES);
    }

    public static function instance()
    {
        if (empty(self::$_instance)) {
            self::$_instance = new Project();
        }

        return self::$_instance;
    }

    public function run()
    {
        list($controller, $action, $params) = $this->_selectController();
        $response = $this->_actionController($controller, $action, $params);
        $response->send();
    }

    private function _selectController()
    {
        $result = $this->_router->resolve($this->_request);
        if (is_null($result)){
            $controller = ProjectSettings::NOT_FOUND_CONTROLLER;
            return [new $controller(), 'index', []];
        }
        return [new $result['class'], $result['action'], $result['params']];
    }

    private function _actionController(Controller $controller, string $action, array $params)
    {
        return $controller->run($action, $params);
    }
}
```

では実際にブラウザで確認してみましょう。 <http://project.com/tasks>
下記の内容が表示されていれば成功です。

```text
Tasks
This is tasks index page from template.
```

ではパスをdetailに変えてブラウザで確認してみましょう。 <http://project.com/tasks/detail>
下記の内容が表示されていれば成功です。

```text
Tasks Detail Page from Template.
・　Title: Create web framework.
・　Status: DOING
```

次にパスを登録されていないパスに変えてブラウザで確認してみましょう。 <http://project.com/>
下記の内容が表示されていれば成功です。

```text
Page not found.
```


## RoutingTableの拡張
とりあえずRoutingができるようになりましたのでちょっと拡張していきましょう。
WebFrameworkでよくみるパスに以下のようなものがあると思います。
- tasks/1 => idが1のタスクを表示する
- tasks/2 => idが2のタスクを表示する

上記のように**動的にパスが指定**できるやつです。
これがないと大変不便なのでこれを実装していきたいと思います。

### Libs/Routing/RoutingTable.php
パスに**動的な数字か文字列**をしていできるようにします。

例:
- detail/str:name => str:nameの部分が可変になります。
- detail/int:id => int:idの部分が可変になります。
- Controllerのアクションのparamsに['id' => ?]と渡るようにします。
- detail/2 とパスが来たらparamsは ['id' => 2]となります

**_pickBranch関数以下**を変更します。

```php:Libs/Routing/RoutingTable.php
<?php
...

class RoutingTable
{
    ...
    
    private function _pickBranch($branch, $piece, &$params)
    {
        if (empty($branch[$piece])) {
            list($real_piece, $params) = $this->_pickIntParam($branch, $piece, $params);
            if($real_piece === false){
                list($real_piece, $params) = $this->_pickStrParam($branch, $piece, $params);
            }
            if($real_piece === false)
                return null;
            $piece = $real_piece;
        }
        $result = $branch[$piece];
        return $result;
    }


    /**
     * @param $branch
     * @param $piece
     * @param $params
     * @return array
     */
    private function _pickIntParam($branch, $piece, $params)
    {
        return $this->_pickParam($branch, $piece, $params, '/^\d+$/', 'int');
    }

    private function _pickStrParam($branch, $piece, $params)
    {
        return $this->_pickParam($branch, $piece, $params, '/^.+$/', 'str');
    }

    /**
     * @param $branch
     * @param $piece
     * @param $params
     * @param $value_pattern
     * @param $value_type
     * @return array
     */
    private function _pickParam($branch, $piece, $params, $value_pattern, $value_type){
        if (preg_match($value_pattern, $piece)) {
            foreach (array_keys($branch) as $key) {
                if (preg_match('/' . $value_type . ':(.+)/', $key, $matches)) {
                    $params[$matches[1]] = $piece;
                    $piece = $key;
                    return [$piece, $params];
                }
            }
        }
        return [false, false];
    }
}
```

では実際にTaskApp/RoutingTable.phpのパスを変更していきます。

```php:TaskApp/RoutingTable.php
...

class RoutingTable extends \Libs\Routing\RoutingTable
{
    protected array $urlPatterns = [
        ['str:name', 'GET', TasksController::class, 'index'],
        ['detail/int:id', 'GET', TasksController::class, 'detail'],
    ];
}

```

TaskControllerも変更します。

```php:TaskApp/Controllers/TasksController.php
...

class TasksController extends Controller
{
    public function index($params)
    {
        $data = ['name' => $params['name']];
        return $this->render('tasks/index', $data);
    }

    public function detail($params)
    {
        $data = ['id' => $params['id'], 'title' => 'Create web framework.', 'status' => 'DOING'];
        return $this->render('tasks/detail', $data);
    }

}
```

テンプレートも動作確認のため変更します。

```php:templates/tasks/index.tmp.php
...

<body>
<h1>Tasks</h1>
This is tasks index page from template.<br>
Name: <?php $escape($name) ?>
</body>

...
```

```php:templates/tasks/detail.tmp.php
...

<body>
<h2>Tasks Detail Page from Template.</h2>

<ul>
    <li>
        ID: <?php $escape($id) ?>
    </li>
    <li>
        Title: <?php $escape($title) ?>
    </li>
    <li>
        Status: <?php $escape($status) ?>
    </li>
</ul>
</body>
...
```

ブラウザで確認してみましょう。 <http://project.com/tasks/john>
**最後の名前の部分**を変えて表示が変わるか確認しましょう。
下記の内容が表示されていれば成功です。

```text
Tasks
This is tasks index page from template.
Name: john
```

次にdetailをブラウザで確認してみましょう。 <http://project.com/tasks/detail/2>
**最後のID部分**を変えて表示が変わることを確認しましょう。
下記の内容が表示されていれば成功です。

```text
Tasks Detail Page from Template.
・　ID: 2
・　Title: Create web framework.
・　Status: DOING
```

# まとめ
１部でとりあえずルーターまで進めることができました。
ここまでの内容でDBとか使わないものなら作れちゃいそうですね。

２部目の内容としましては、DBを使ったTaskAppの作成、Sessionを利用したユーザー認証機能の作成、Middlewareの実装という風に考えてます。
[PART2はこちら](https://qiita.com/sakunowman/items/031d135f7d2d9451c32e)