Slim(PHP)メモ


# 前置き
簡単な問い合わせフォームみたいなものを短期間で作ることになった。

短期間で作成できて対応サーバが多そうなPHPを選択したものの素のPHPは使いたくないし、だからって大きめのフレームワークは避けたかったのでマイクロフレームワークとされていたSlimを使ってみた。

Sinatraを少しだけ使ったことはあったのでアプリケーション部分はそんなに悩むこともなく使えた。ちょっとしたウェブアプリケーションを作るんだったら充分。


# composer
Composerを使うのも初めてなので一応メモ。

## composerの準備
最初はHomebrewからインストールしたんだけどインストールスクリプトが単体で落とせるんですね。
サーバにデプロイする際にも使えるのでこちらの方が良い。

    curl -sS https://getcomposer.org/installer | php


## composer.jsonを作成
必要なパッケージを記述する。
最近はTwigというビューテンプレートが流行ってるらしいけど、小規模だしデザインはhtmlでもらっているので面倒なのでやめておいた。レイアウトファイルは欲しかったのでslim-layout-viewを使用。

あと、今回はメール送信が必要なのでswiftmailerを追加。

    {
      "require": {
        "slim/slim": "2.*",
        "petebrowne/slim-layout-view": "0.3.*",
        "swiftmailer/swiftmailer": "@stable"
      }
    }


## インストール
インストールコマンドを叩けばvendor以下に必要なものをセットアップしてくれるらしい。

    ./composer.phar install

あとはvendor/autoload.phpをrequireすれば使える。


## ファイル構成
Slimの場合、配置ルールにほとんど制約はないらしいけど、好き勝手に置くわけにはいかないので以下の様なルールを適用。

    プロジェクトルート/
      composer.json    # Composerの構成ファイル
      composer.lock    # Composerのロックファイル
      composer.phar    # Composerのインストールスクリプト
      public/          # ドキュメントルート
        css/           # CSS置き場
        img/           # 画像置き場
        .htaccess      # apacheで配置する場合の設定ファイル
        web.config     # IISで配置する場合の設定ファイル
        index.php      # Slimのメインファイル
        index.html     # トップページ
        config.php     # 設定ファイル
      app/
        forms/         # フォームクラス置き場
        logs/          # ログの出力先
        mail/          # メール送信クラス置き場
          templates/   # メールテンプレート置き場
        routes/        # ルーティングとアクションの定義
        views/         # ビューファイル置き場
          layouts/     # レイアウトファイル置き場
      vendor/          # Composer経由のライブラリ類インストール先


### public/index.php
アプリケーションの起点。このファイルで完結しても良いらしいけど、各ページのアクションを記述するのは避けたかったので別ファイルに分割。コントローラみたいな仕組みは無いらしいし、そこまでする必要もなさそうだったのでrequireするだけにした。

セッション管理は何かライブラリを使ったほうが良いのかもしれない。

あと、設定ファイルを切り出して、開発環境と本番環境で分けられるようにしたい。
設定自体はSlimに押し付けることはできるらしいので要調査。

```php
<?php

// タイムゾーンの設定
date_default_timezone_set('Asia/Tokyo');

// 開発用のサーバの場合はデバッグモード
if(preg_match('/Development/', $_SERVER["SERVER_SOFTWARE"])){
  define('APPLICATION_ENVIRONMENT', 'development');
  define('DEBUG_MODE', true);
}else{
  define('APPLICATION_ENVIRONMENT', 'production');
  define('DEBUG_MODE', false);
}


require '../vendor/autoload.php';

// ロガーの定義
$today = new DateTime('now', new DateTimeZone("Asia/Tokyo"));
$filepath = sprintf('../app/logs/%s.log', $today->format('Y-m-d'));
$logWriter = new \Slim\logWriter(fopen($filepath, 'a'));

// アプリケーションの作成
$app = new \Slim\Slim(array(
    "debug" => DEBUG_MODE,
    'mode' => APPLICATION_ENVIRONMENT,
    "templates.path" => "../app/views",
    'view' => '\Slim\LayoutView',
    'layout' => 'layouts/main.phtml',
    'log.writer' => $logWriter
));

session_start();

// ルーティングファイルの読み込み
require '../app/routes/index.php';
require '../app/routes/inquiry.php';

// アプリケーションの実行
$app->run();
```

### app/routes/inquiry.php
分割したルーティングとアクションの記述。シンプルに$appをそのまま利用している。
先頭でrequireしてるけどautoloadで通るようにできるんじゃないかな。

```php
<?php

require_once '../app/forms/inquiry.php';
require_once '../app/mail/inquiry.php';
require_once '../app/mail/inquiry_confirmation.php';

// 入力
$app->get('/inquiry', function() use ($app){
  $params = $app->request->post();

  $form = new \Forms\Inquiry();
  if(count($params) > 0){
    $form->validate($params);
  }
  $app->render('inquiry/index.phtml', array(
    'form' => $form
  ));
});
```

### app/forms/inquiry.php
超シンプルに入力値の検証とエラーメッセージの管理をしてるだけ。

```php
<?php

namespace Forms;

class Inquiry
{
  private $_values = array();
  private $_errors = array();

  public function __construct()
  {
  }

  public function validate($values)
  {
    $this->_errors = array();

    $this->setValue('last_name', $this-> nullOrValue($values, 'last_name'));
    if ($this->getValue('last_name') == '') {
      $this->addMessage('last_name', '姓は必須項目です。');
    }

    return count($this->_errors) <= 0;
  }

  public function getMessages()
  {
    return $this->_errors;
  }

  public function getErrorCount()
  {
    return count($this->_errors);
  }

  public function addMessage($name, $message)
  {
    $this->_errors[$name] = $message;
  }

  public function getMessage($name)
  {
    return $this->null_or_value($this->_errors, $name);
  }

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

  public function getValue($name)
  {
    return $this->null_or_value($this->_values, $name);
  }

  private function nullOrValue($params, $key)
  {
    return isset($params[$key]) ? $params[$key] : null;
  }
}
```

### app/mail/inquiry.php
メール送信クラス。本文はmail/templates以下に置いたファイルをPHPとして読み込んで作る。
メール送信に関する情報はSlimにconfigとして持たせている。

```php
<?php

namespace Mail;

class Inquiry
{
  private $_form;
  private $_app;

  public function __construct($form, $app)
  {
    $this->_form = $form;
    $this->_app = $app;
  }

  protected function getForm()
  {
    return $this->_form;
  }

  protected function getApplication()
  {
    return $this->_app;
  }

  public function getMailTo()
  {
    return $this->getApplication()->config('mail_to');
  }

  public function getMailFrom()
  {
    return $this->getApplication()->config('mail_from');
  }

  public function getSubject()
  {
    return $this->getApplication()->config('mail_subject');
  }

  public function getTransport()
  {
    $mailer = \Swift_SmtpTransport::newInstance($this->getApplication()->config('smtp_host'), $this->getApplication()->config('smtp_port'));

    // アカウントの指定が有る場合
    if($this->getApplication()->config('smtp_user')){
      $mailer->setUsername($this->getApplication()->config('smtp_user'));
      $mailer->setPassword($this->getApplication()->config('smtp_password'));
    }

    return $mailer;
  }

  public function send()
  {
    $transport = $this->getTransport();

    // メーラーの作成
    $mailer = \Swift_Mailer::newInstance($transport);

    // 送信ログ
    $this->getApplication()->getLog()->info($this->getBody());

    // メッセージ作成
    $message = \Swift_Message::newInstance()
        ->setSubject($this->getSubject())
        ->setTo($this->getMailTo())
        ->setFrom($this->getMailFrom())
        ->setBody($this->getBody());
    $mailer->send($message);
  }

  public function getBody()
  {

    $form = $this->getForm();

    // テンプレートファイルの読み込み(PHP)
    $content = file_get_contents('../app/mail/templates/inquiry.php');
    eval('$result = "' . $content . '";');

    return $result;
  }
}
```

## app/config.php
アプリケーションの設定値を記述。Slimに渡したModeに従ってそれぞれの環境での設定を行っている。
開発環境の場合はmailcatcherを使っているのでSMTP設定はそれにあわせている。

```php
<?php

// メールタイトル
$app->config('mail_subject', '問い合わせ');

// 開発
$app->configureMode('development', function() use ($app) {
  $app->config('log.level', Slim\Log::DEBUG);

  // STMPサーバ
  $app->config('smtp_host', '127.0.0.1');
  $app->config('smtp_port', '1025');

  // メール送信先
  $app->config('mail_to', 'hoge@fuga.com');

  // メール送信元
  $app->config('mail_from', array('hoge@fuga.com' => 'piyo'));
});



// 本番
$app->configureMode('production', function() use ($app) {
  $app->config('log.level', \Slim\Log::INFO);

  // STMPサーバ
  $app->config('smtp_host', '127.0.0.1');
  $app->config('smtp_port', '587');

  // メール送信先
  $app->config('mail_to', 'hoge@fuga.com');

  // メール送信元
  $app->config('mail_from', array('hoge@fuga.com' => 'piyo'));
});

```

## ビューについて
ビューの部分は単なるHTMLベースのPHPスクリプトなので省略。


# 参考

[PHP - composer 導入をまじめに考える - Qiita](http://qiita.com/notona/items/c5a087d8dd446d315e6e)
[php軽量フレームワークslimでちょっとしたアプリケーションを作る - Qiita](http://qiita.com/ichi944/items/f4134dd27a3057d359b0)