PHP Slim 環境構築(12) 前回構築した環境をEC2上に

# PHP Slim 環境構築(12) 前回構築した環境をEC2上に


## Introduction

[前回](https://qiita.com/dynamitecoolguy/items/73895292d4c59cb91394)は、DockerコンテナにNginxとPHP-FPMの両方を格納した構成をローカル環境に構築しました。
今回は、それをEC2上に構築します。

この一連のシリーズは、自分への備忘録が第一目的のため、相当不親切です。
すみません・・・


## 変更点

今回構築した環境では、ローカルPC上に構築したものをlocal環境、EC2上に構築したものをdevelopment環境と呼ぶことにしています。
今回は、前回作成したlocal環境をdevelopment環境にも適用するとともに、ソースを少し整理しました。


#### ソースツリー

今回の変更箇所です。

```
$(PROJECTROOT)
  /aws
    ec2-userdata.txt                       (新規)
  /compose
    /web_front
      development.conf
    /web_hoge
      Dockerfile
      settings.php                         (削除)
      settings-development.yml             (新規)
      settings-local.yml                   (新規)
  /src
    /hoge
      /lib
        /Controller
          DynamodbController.php
          StorageController.php
        MyContainerBuilder.php              (新規)
      /public
        index.php
      /test
        RequestTest.php
      composer.json
      composer.lock
```

### /aws/ec2-userdata.txt

EC2のユーザーデータをリポジトリに追加しました。

```text:/aws/ec2-userdata.txt
#!/bin/bash
cat <<'SYS_EOF' >> /etc/sysctl.conf
net.core.somaxconn=65535
SYS_EOF

sysctl -p /etc/sysctl.conf

cat > /etc/security/limits.d/20-filelimits.conf <<'FLIMIT_EOF'
*    soft    nofile    32768
*    hard    nofile    32768
FLIMIT_EOF
ulimit -n 32768

amazon-linux-extras install docker -y
systemctl enable docker
usermod -a -G docker ec2-user
service docker start

curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

yum install -yq git

cd /home/ec2-user
git clone https://github.com/dynamitecoolguy/slimtemplate.git
cd slimtemplate
git checkout chapter12
chown -R ec2-user:ec2-user .

cd compose
docker-compose -f docker-compose-common.yml -f docker-compose-development.yml up -d
```

### /compose/web_front/development.conf

前回変更したlocal.confと同様に、nginxは単なるreverse proxyになります。

``` conf:/compose/web_front/development.conf
ssl_certificate /etc/certs/server.crt;
ssl_certificate_key /etc/certs/server.key;

ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

server {
  listen 3128 default_server;
  listen 8443 ssl default_server;
  server_name _;
  access_log /dev/stdout;
  error_log /dev/stderr;

  proxy_set_header Host            $host;
  proxy_set_header X-Real-IP       $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  location / {
    proxy_pass http://web_hoge:3128/;
  }
}
```

### /compose/web_hoge/Dockerfile

環境用設定ファイルをPHP直書きから、ymlに変更しました。
また、各環境用の設定ファイルを用意することにしました。

```dockerfile:/compose/web_hoge/Dockerfilee
...(略)...
# COPY settings.php /var/www/settings.php
COPY settings-${environment}.yml /var/www/settings.yml
...(略)...
```

### /compose/web_hoge/settings-development.yml,  settings-local.yml

以前までのsettings.phpをymlに変更しました。また、ソースに直書きしていたキーなどもこのファイルに抜き出しました。
なお、settings-development.ymlとsettings-local.ymlの内容は同じです。

```yml:/compose/web_hoge/settings-development.yml
userdb:
  host: mysql
  dbname: userdb
  user: scott
  password: tiger

logdb:
  host: postgresql
  dbname: logdb
  user: root
  password: hogehoge

redis:
  host: redis

dynamodb:
  endpoint: http://dynamodb:8000
  region: ap-northeast-1
  key: dummy-key
  secret: dummy-secret
  table: hogehoge

storage:
  endpoint: http://storage:9000
  region: ap-northeast-1
  key: minio
  secret: miniminio
  bucket: dummy
```

### /src/hoge/lib/Controller/DynamodbController.php, StorageController.php

ソースコードに直接記述していたdynamoDBのテーブル名、S3のバケット名を設定ファイルを参照するようにしました。

```php:/src/hoge/lib/Controller/DynamodbController.php
...(略)...
$result = $dynamodb->describeTable([
    //'TableName' => 'hogehoge'
    'TableName' => $this->getTable()
]);

...(略)...
...('hogehoge'を変更した箇所が後3か所)...
...(略)...

private function getTable(): string
{
    $setting = $this->container->get('settings')['dynamodb'];
    return $setting['table'];
}
...(略)...
```

```php:/src/hoge/lib/Controller/StorageController.php
...(略)...
//$s3->headBucket(['Bucket' => 'dummy']);
$s3->headBucket(['Bucket' => $this->getBucket()]);

...(略)...
...('dummy'を変更した箇所が後3か所)...
...(略)...

private function getBucket(): string
{
    $setting = $this->container->get('settings')['storage'];
    return $setting['bucket'];
}
...(略)...
```

### /src/hoge/lib/MyContainerBuilder.php

設定用ymlファイルを読み込み、必要な定義を組み込み済みのContainerBuilderです。
DI\ContainerBuilderを継承しています。

```php:/src/hoge/lib/MyContainerBuilder.php
<?php

namespace Hoge;

use Aws\Sdk;
use PDO;
use Psr\Container\ContainerInterface;

use DI\Container;
use DI\ContainerBuilder;
use Redis;

class MyContainerBuilder extends ContainerBuilder
{
    public function __construct(string $containerClass = Container::class)
    {
        parent::__construct($containerClass);

        $this->addDefaultDefinitions();
    }

    private function addDefaultDefinitions(): void
    {
        $parsed = yaml_parse_file(__DIR__ . '/../../settings.yml');

        $this->addDefinitions(['settings' => $parsed]);
        $this->addDefinitions([
            'userdb' => function (ContainerInterface $container)
            {
                $settings = $container->get('settings')['userdb'];
                $dsn = 'mysql:host=' . $settings['host'] . ';dbname=' . $settings['dbname'];
                $options = [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false
                ];
                return new PDO($dsn, $settings['user'], $settings['password'], $options);
            },
            'logdb' => function (ContainerInterface $container)
            {
                $settings = $container->get('settings')['logdb'];
                $dsn = 'pgsql:host=' . $settings['host'] . ';dbname=' . $settings['dbname'];
                $options = [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false
                ];
                return new PDO($dsn, $settings['user'], $settings['password'], $options);
            },
            'redis' => function (ContainerInterface $container)
            {
                $settings = $container->get('settings')['redis'];
                $redis = new Redis();
                $redis->connect($settings['host']);
                $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY);

                return $redis;
            },
            'dynamodb' => function (ContainerInterface $container)
            {
                $settings = $container->get('settings')['dynamodb'];

                $sdk = new Sdk([
                    'endpoint' => $settings['endpoint'],
                    'region' => $settings['region'],
                    'version' => '2012-08-10',
                    'credentials' => [
                        'key' => $settings['key'],
                        'secret' => $settings['secret']
                    ]
                ]);

                $dynamodb = $sdk->createDynamoDb();
                return $dynamodb;
            },
            'storage' => function (ContainerInterface $container)
            {
                $settings = $container->get('settings')['storage'];
                $sdk = new Sdk([
                    'endpoint' => $settings['endpoint'],
                    'region' => $settings['region'],
                    'version' => '2006-03-01',
                    'credentials' => [
                        'key' => $settings['key'],
                        'secret' => $settings['secret']
                    ],
                    'use_path_style_endpoint' => true
                ]);
                $s3 = $sdk->createS3();
                return $s3;
            }
        ]);
    }
}
```


### /src/hoge/public/index.php

DI部分を上記のMyContainerBuilderに分割したので、必要ない部分を削除しました。

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

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Routing\RouteCollectorProxy;

use Hoge\ExampleAfterMiddleware;
use Hoge\ExampleBeforeMiddleware;
use Hoge\MyContainerBuilder;
use Hoge\Controller\PlayerController;
use Hoge\Controller\PlayerCreatedLogController;
use Hoge\Controller\RedisController;
use Hoge\Controller\DynamodbController;
use Hoge\Controller\StorageController;

/** @var Composer\Autoload\ClassLoader $loader */
$loader = require __DIR__ . '/../../vendor/autoload.php';
$loader->addPsr4('Hoge\\', __DIR__ . '/../lib');

$containerBuilder = new MyContainerBuilder();
$container = $containerBuilder->build();

AppFactory::setContainer($container);
$app = AppFactory::create();

// Middleware
$contentLengthMiddleware = new \Slim\Middleware\ContentLengthMiddleware();
$app->add($contentLengthMiddleware);

$app->get('/', function (Request $request, Response $response, array $args) {
    $response->getBody()->write('Hello, World!');
    return $response;
})
    ->add(new ExampleBeforeMiddleware())
    ->add(new ExampleAfterMiddleware());

$app->group('/player', function (RouteCollectorProxy $group) {
    $group->get('/{id}', PlayerController::class . ':get');
    $group->post('', PlayerController::class . ':post');
    $group->put('/{id}', PlayerController::class . ':put');
});

$app->group('/player_created', function (RouteCollectorProxy $group) {
    $group->get('/{id}', PlayerCreatedLogController::class . ':get');
    $group->get('', PlayerCreatedLogController::class . ':list');
});

$app->group('/redis', function (RouteCollectorProxy $group) {
    $group->get('/{key}', RedisController::class . ':get');
    $group->post('', RedisController::class . ':post');
    $group->put('/{key}', RedisController::class . ':put');
    $group->delete('/{key}', RedisController::class . ':delete');
});

$app->group('/dynamodb', function (RouteCollectorProxy $group) {
    $group->get('/{key}', DynamodbController::class . ':get');
    $group->post('', DynamodbController::class . ':post');
});

$app->group('/storage', function (RouteCollectorProxy $group) {
    $group->get('/{filename}', StorageController::class . ':get');
    $group->post('', StorageController::class . ':post');
});

$app->run();
```


### /src/hoge/test/RequestTest.php

テスト用コードも、設定用ymlから情報を取り込むように変更しました。
(ソースコード略)。

### /src/hoge/composer.json, compser.lock

yamlの使用に伴い、composer require yamlを行いました。

```json:composer.json
{
    "require": {
        "slim/slim": "4.2.0",
        "slim/psr7": "0.5.0",
        "php-di/php-di": "6.0.9",
        "ext-pdo": "^7.2",
        "ext-json": "^1.6",
        "ext-redis": "^3.1",
        "ext-curl": "*",
        "ext-yaml": "*",
        "aws/aws-sdk-php": "^3.112"
    },
    "require-dev": {
        "phpunit/phpunit": "^8.4"
    }
}
```


## ここまでのソース

[こちら](https://github.com/dynamitecoolguy/slimtemplate/tree/chapter12)でどうぞ。