またどこかのCTOになった人のブログ

またどこかのCTOになった人が書いてます。最近はCakePHP3とConoHaのネタが多いです。

稼働中のメールサーバ経由で、別サーバからメール送信してみた。

偉そうにサーバ構築なんてやっています。
まぁ常にタコ糸で綱渡りしているのは間違い無いのですが。
カンニングペーパー代わりのEvernoteが成長しています。

さて。
今回は偉そうなことをやってみました。

やりたかったこと

ConoHaで立てた稼働中のメールサーバ経由で、別サーバからメール送信する必要が出て来ました。
別サーバというのはWordpressのサーバなので、プラグイン使ってポイーっとメール送信すればいいのですが、そのサーバ上にランディングページ用の別ディレクトリを切って、そこに配置したお問い合わせフォームからメール送信という仕様です。

え。普通にWordpressに置けばいいじゃん。

とも思ったのですが仕方がありません。
あまり時間もなかったので、お問い合わせフォームはこちらを使わせて頂きました。

www.php-factory.net

どうもありがとうございます。

ちょっと考えてみた

アレです。
要はフォームが載っているサーバから、メールサーバの送信情報を使ってサクっとやればいいのです。
かんたん。かんたーん。

と思ったのが間違いでした。
よく思い出せばよかったのです。自分はインフラが嫌いだということを。
ここ最近、サーバをさわる機会が多かったので勘違いしていました。

以下、このように記載することにします。

・サーバA:Wordpressが載っているサーバ
・サーバB:メールサーバ(固定IPアドレスを振っています)

と、操作はrootでやってます。
OSはCentOS7です。
apacheは2.4です。

とりあえずsendmailをインストールしてみた

既に入っていればいいですが、入ってなかったらインストールしましょう。
CentOSって最初から入ってなかったっけ、などと思いながらyumちゃんにお願いしました。

$ yum install sendmail
$ yum install sendmail-cf

yumちゃんがいい感じにインストールしてくれます。
sendmail-cfの方は後で使うかもしれませんので、まとめて入れてしまいましょう。
ちなみに今回は使ってません。

何となくサーバAのホスト名を変更してみた

何かよくわかりませんが、ホスト名を変更してみました。

$ hostnamectl set-hostname xxxxx.com

上記のコマンドでホスト名が変わります。
変わるのですがこのままだとちょっと都合が悪いので、以下の処理も続けて実施します。

$ vim /etc/cloud/cloud.cfg

# 「- update-hostname」部分をコメントアウト
# - update-hostname

やってやりました。
ホスト名なのですが、自分の場合「メールドメインWordpressサーバドメインが同一」だったので、こちらのホスト名は別の名前に変えてしまいました。
srv.xxxxxxx.comみたいな感じです。

SMTP送信設定をしてみた

いよいよコアな部分です。ドキドキします。
sendmailをインストール後、「/etc/mail/」配下のファイルをごにょごにょします。

ここにsendmailの設定ファイルの数々がいらっしゃいます。
次にSMTP送信設定用の「authinfo」とかいうそれっぽい名前のファイルを開きましょう。
以降のvimコマンドなどは、こちらのディレクトリ内で叩いているという解釈でお願いします。

$ vim /etc/mail/authinfo

開いたらここにSMTP送信設定を記載します。
SMTP送信設定はメールサーバの方にきっとあることでしょう。
2行でセットです。

AuthInfo:SMTPサーバホスト名 "U:メール送信元の対象アドレス" "P:メール送信元の対象アドレスパスワード" "M:PLAIN"
AuthInfo:SMTPサーバホスト名:ポート番号 "U:メール送信元の対象アドレス" "P:メール送信元の対象アドレスパスワード" "M:PLAIN"

SMTPサーバホスト名部分がIPアドレスでもOKなのかは試してないです。
まぁホスト名をブチ込んでおけばいいんじゃないかと。
ConoHaのメールサーバの場合はサーバ詳細の部分に書いてあります。
多分、以下のような感じになるかと思います。

AuthInfo:smtp.xxxxx.aaaaaa.com "U:info@xxxxxx.com" "P:abcdefg12345" "M:PLAIN"
AuthInfo:smtp.xxxxx.aaaaaa.com:587 "U:info@xxxxxx.com" "P:abcdefg12345" "M:PLAIN"

ポート番号は大部分が「587」だとは思いますが、メールサーバに合わせて適宜修正して下さい。

sendmail.mcを修正して、sendmail.cfを作成することにした

ボスの登場です。
こいつが手強かったです。
が、以下のような感じの修正でできると思います。

sendmailの送信にはsendmail.cfを使用するのですが、sendmail.mcファイルを修正してcfファイル(コンフィグファイルらしいです)を生成するのが一般的だそうです。

$ vim /etc/mail/sendmail.mc

## 以下の設定を編集して下さい。
## コメントアウトするものと追記が必要なものは一応書いてみました

define(`confAUTH_OPTIONS', `A')dnl #コメント解除
define(`confAUTH_MECHANISMS', `EXTERNAL GSSAPI DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl #コメント解除
TRUST_AUTH_MECH(`EXTERNAL GSSAPI DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl #追記する
FEATURE(`authinfo', `Hash -o /etc/mail/authinfo')dnl #追記
define(`SMART_HOST', `SMTPサーバホスト名')dnl #コメント解除して編集
define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl #追記
define(`ESMTP_MAILER_ARGS', `TCP $h 587')dnl #追記
dnl DAEMON_OPTIONS(`Port=smtp,Addr=127.0.0.1, Name=MTA')dnl #コメントアウト
DAEMON_OPTIONS(`Port=smtp, Name=MTA')dnl #追記
LOCAL_DOMAIN(`設定したサーバAのホスト名')dnl #コメント解除して編集
MASQUERADE_AS(`メール送信するサーバBのドメイン名')dnl #コメント解除して編集
FEATURE(masquerade_envelope)dnl #コメント解除

雑ですみません。
.mcファイルさんはコメントアウトに「dnl」という謎の文字列を使うそうです。
見慣れませんが仕方がないです。

accessファイルを編集してみる

このファイルはメールのリレー設定に使うファイルです。
否認とかにも使えます。
その辺はGoogle先生がきっと教えてくれます。

今回はサーバAから送信する時、サーバBを使いたいだけなのでリレー設定だけです。
偉そうに書いてる風味ですが、あまりよくわかっていません。

$ vim /etc/mail/access

Connect:サーバBのIPアドレス  RELEY

きっと「指定したIPアドレスにリレーしますよ!」っぽい何かだと思います。
多分。

mailertableとかいうものも編集してみる

これは「送信元のメールドメインはここですよ!」ファイルだと思われます。
そんな感じでいいんじゃないですかね!

$ vim /etc/mail/mailertable

送信元のメールドメイン  smtp:SMTPサーバホスト名

実際の記載例だとこんな風になると思います。

sample.com  smtp:smtp.sample.com

smtp:〜部分はダブルクォーテーションなどで囲わないようにして下さい。
何か上手くいかなかった模様です。

できたっぽい気がするのでsendmailを再起動してみる

上記の設定を反映させるために再起動します。
ひとつひとつの設定からファイルを生成する方法もあるのですが、ボスファイルである「sendmail.mc」の方に再起動したら新しいファイル作ってやるぜ設定が書いてあるので、sendmail再起動でいいかと思います。

$ systemctl restart sendmail

再起動コマンドはOSによって違うと思いますので、その辺はフィーリングで。

ついでにapacheも再起動しておきました。

$ systemctl restart httpd

うまく設定できたか試してみた

と、上記の設定が上手くできていればいいなと祈りながら、以下のコマンドを打ってみましょう。

$ sendmail -bv メールアドレス
# 実際には送信されません
# sendmail -bv aaaaa@sample.com みたいな感じでコマンドを打ちます

これを実行して上手いことできていると、以下のようなログが流れます。

$ sendmail -bv aaaaa@sample.com
aaaaa@sample.com... deliverable: mailer smtp, host smtp.xxxxxx.sample.com, user aaaaa@sample.com

で き て い る

そもそも何故こんなことをしたのか

問い合わせが入った時、管理側と投稿者に「問い合わせきたよー」メールを送信したかったのです。
投稿者の方は問題ないのですが、管理側の方が上手くいきませんで。
よくよく考えたら、ローカルホストに対してローカルホストユーザにメールをするわけなので、そのユーザがいないと「User unknown」と無慈悲なエラーログが表示されてしまいます。

で、今回やってみたのですが果たしてこれでいいのか。
少々不安なのですが、概ねいいでしょう。

またどこかのCTOになってみた。

そう言えば、7月からジョインした会社でCTOになりました。
エンジニアは自分1人なんですけども。

孤独なエンジニアですが結構楽しくやっております。
来年の今頃は4〜5人エンジニアいたらいいなぁなどと期待しています。
アレもコレも作りたいのです。

エンジニアが増えても当然開発やります。
好きな業務はコーディング。
マネジメントなんてフィーリング。組織作りはノリとテンション。
雑談担当からお酒の相手まで幅広くカバー。
こんなCTOがいてもいいんじゃないかと思います。

あ、やる時はちゃんとやるんです、これでも。
いや、ホントに。


というわけなので、またブログタイトルを変えてみました。
内容は特に変わりませんが、宜しくお願い致します。

CakePHP3でunlockFieldとやらを使ってみた。

実はJavaScriptも嫌いです。
嫌いなものばかりなのにエンジニアとか言ってていいんでしょうか。
いいとか悪いとかではなく、ここまで来てしまうと言い続けるしかありません。

さて、本題。
なんですが、今回のネタは合っているのかどうかよくわかりません。
「何かよくわかんないけどできちゃったぞ」的なものなので、その辺りご了承ください。

やりたかったこと

hidden項目として設定したフィールドに対して、AjaxとかJavaScriptとかでごにょごにょした結果を格納してSubmitする。

結果

Securityコンポーネントから怒られた。


困りました。非常に困りました。
困ったのですが、そりゃデフォルトの値が書き換えられてるわけなので怒るのも当たり前です。
だってSecurityコンポーネントですもの。
でも、どうにかしたい。

よくある対応方法

対象のActionが実行された時にSecurityコンポーネントをOFFにすれば動きます。
なんですが、Submitするってことは何かしらの編集処理なわけなので、そのActionをまるっとOFFにするのって引っかかります。
それが正しい処理なのかもしれませんが、個人的にはちょっとアレです。

なのでSecurityコンポーネントをOFFにする書き方は書かないことにします。
気になる方はGoogle先生にお申し立てを。

今回やってみた対応方法

「書き換え対象のフィールドだけSecurityコンポーネント対象外にする」というやり方です。
ここでやっとunlockFieldが登場します。

公式にはこんな風に書いてあります。

セキュリティ - 3.x

unlockedFields
POST バリデーションを解除したいフォームフィールドの一覧をセットします。 このコンポーネントの他にも、 FormHelper::unlockField() でも解除できます。制限が解除されたフィールドは、POST 時に必須ではなくなり、 hidden フィールドの値もチェックされません。

ナニソレ、イミワカンナイ。


この説明で他の方々わかるんですかね。
自分にはさっぱり理解できませんでした。


理解できない時はとりあえず色々アレコレ書いて試すタイプなので、色々試しました。
「あー。もうできないからPOSTする時にモデルからもう一回取得すっかな」などと邪なことも考えながら。

結局こんな風になりました

unlockedFieldsとやらをセットするのはView側です。
こんなソースにしてみました。

  <?php echo $this->Form->input('sample_id', ['type' => 'hidden']); ?>
  <?php echo $this->Form->unlockField('sample_id'); ?>

これだけです。
Ajaxで処理した時に上記ソースのsample_idに何か値がセットされます。
セットされてたら更新、されてなかったら新規登録みたいなことをやりたかったのです。
unlockFieldは何なんでしょうね。
無視しといて。よろしくみたいな感じなんでしょうか?
よくわかりません。

こんな書き方をすると、unlockFieldが有効になりSecurityコンポーネントさんも怒りません。
まぁとりあえずは結果オーライ、大勝利です。

合ってるのかなこれという不安は消えませんが、概ねいいでしょう。
正しい使い方を知ってる方いたら教えてください。

CakePHP3で画像データをDBに保存してみた。

その昔、こんな記事を書きました。
tsuralabo.hatenablog.com


画像データのDB登録記事がないことに1年近く経って気がつきました。
これはいけません。
ということで何事もなかったかの如く、記事を書くことにします。


DBへの画像登録というのは、いわゆるバイナリ登録です。
BLOBデータのアレですね。
昔から「BLOB」と聞くとスライム的な何かを想像してしまうのですが、それはどうでもいいです。
きっと過去にプレイしたRPGの何かなんじゃないかと思いますが、それもよくわかりません。


さて。
画像をDB登録する際はフォームヘルパーのFile属性に対して画像を指定して、それをアップロードするのが多いんじゃないかと。
それ以外もあるのかもしれませんが、自分が見たことないだけかもしれません。
とりあえず、View側の画像アップロード部分はこんな感じで作ります。

  <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
    <span>画像</span>
    <div class="input-group">
      <span class="input-group-addon">
        <i class="glyphicon glyphicon-paperclip"></i>
      </span>
      <?php echo $this->Form->file('sample_image_id', [
         'class' => 'form-control', 
         'type' => 'file', 
         'div' => false,
         'label'=> false
      ]); ?>
    </div>
  </div>


Bootstrap3使ってます。ちょっと怪しい書き方ですが、その辺は生暖かくお願いします。
さて、ここに指定された画像データがsubmitされた時、以下のようにごにょっとすると登録できます。

<?php
  /**
   * 画像登録処理(アップロードされた画像をそのまま登録)
   * @param type $imageFileModel    image_filesテーブルのモデル
   * @param type $imageFile              登録する画像本体
   */
  public function saveImageFile($imageFileModel, $imageFile) {
    
    // 画像情報を取得
    $imginfo = getimagesize($imageFile['tmp_name']);

    // ファイル拡張子に応じて処理
    if ($imginfo[2] == IMAGETYPE_JPEG) {
      // JPEG画像データを生成
      $createImage = imagecreatefromjpeg($imageFile['tmp_name']);
    } else if ($imginfo[2] == IMAGETYPE_PNG) {
      // PNG画像データを生成
      $createImage = imagecreatefrompng($imageFile['tmp_name']);
    }

    // 画像データをバッファへバイナリ出力
    ob_start();
    imagepng($createImage);
    $imageBinary = ob_get_clean();

    // モデル登録用パラメータ生成
    $imgFileParam = [
      'content_type' => $imageFile['type'],
      'image_data' => $imageBinary
    ];

    // 新規登録用エンティティ生成
    $entity = $imageFileModel->newEntity($imgFileParam);

    // 画像保存処理実行
    $result = $imageFileModel->save($entity);

    // 画像メモリを解放
    ImageDestroy($createImage);

    // データ登録時のIDを返す
    return $result->id;
  }


自分のよくやるパターンだと、上記のfunctionをコンポーネントに入れてます。
で、コントローラの方でそのコンポーネント経由でfunction呼び出してます。
「tmp_nameってなんだよ」ってのはview側からsubmitすればわかります。
「pr($this->request->getData());」とかやってみると出てくると思います。

トランザクションに関しては呼び元のコントローラの方でやっているのですが、環境や仕様に合わせて適宜修正して下さい。
一番最後にIDを返していますが、画像登録用テーブルと実テーブルが別ですのでIDを取得して実テーブルにパラメータセットしているためこんな感じになっています。ここも適宜修正して頂けますと。

getimagesize部分は対象画像のデータが色々取れますので、幅とか高さとかその辺の制限があればここで取得を。
でも登録処理する前にバリデーションは別でやりたいんだよ! という方は、上記functionの前にバリデーションを。
箇条書きっぽくなりましたが、以下を参考にして下さい。

<?php
  // $params['sample_image_id']でFile属性のデータが渡ってくることにします
  // 拡張子判定の場合
  $extension = substr($params['sample_image_id'], strrpos($params['sample_image_id'], '.') + 1);
  
  // $extensionに拡張子が入るので判定。
  // 判定用のリストを作っておくのもいいかもしれません
  private $extensionList = ['jpg', 'png'];

  if (!in_array($extension, $this->extensionList)) {
      $errorMessage[] = '画像の拡張子が不正です。jpgファイル、またはpngファイルを指定して下さい。';
  }


  // サイズ判定の場合
  $fileSize = $params['sample_image_id']['size'];

  // そもそもアップロードされてるかどうか判定
  if ($params['sample_image_id']['error'] == 0) {
    // アップロードされた
  } else {
    // アップロードされてない
    // $params['sample_image_id']['error']が「4」とかだと思います 
  }


こんな感じで画像データをDBにバイナリ登録したり、バリデーション実行したりするといいんじゃないかと思います。
でも、多分もっといい書き方あると思います。

まず動くことが大事。

動いてから色々ごにょごにょするといいかなと。

CakePHP3でリスト取得時のカラムを連結させてみた。

気がついたら8月が終わっていました。
あっという間に2017年もあと4ヶ月です。時間の流れの速さが恐ろしいです。

さておき。
ラジオボタンがあまり好きじゃなくてセレクトボックスの方が好きなタイプなのですが
CakePHP3だとこんな風にしてクエリを投げてView側にセットしています。
多分合ってます。

<?php
  // usersモデルを使うということにします
  $userModel = TableRegistry::get('Users');
  $userList = $userModel->find('list'), [
    'keyField' => 'id',
    'valueField' => 'user_name'
  ])
  ->toArray();

  // ViewにセットしてView側のセレクトボックスのoptionsにセット
  $this->set('userList', $userList);

インデントが様子おかしいかもしれませんが、こんな感じです。
リスト取得部分はモデルに書いた方がすっきりするかもしれません。
その辺はいつものアレですが好みです。

で。
上の例ですと「user_name」を画面上に表示するような雰囲気になりますが、例えばこれが苗字と名前に分かれていたとしたらセレクトボックスの方で連結させたいなーとなるんじゃないかと思います。
カラムだと何でしょう。family_nameとfirst_nameとかになるんですかね。
まぁその辺はどうでもいいですが、今回はそういうことにします。

文字列連結させたら終わり! と思っていたのですがやってみたら上手くいきませんでした。
いや、上手くいくかもしれませんが何だかできなかったのです。
仕方がないのでごにょごにょして実装したソースが以下です。

<?php
  // またusersモデルを使うということにします
  $userModel = TableRegistry::get('Users');
  $userList = $userModel->find('list'), [
    'keyField' => 'id',
    'valueField' => function ($entity) {
       // この部分をEntityから取得する
       // 変数は$enitityじゃなくて構いません。適当に
       return $entity->user_full_name;
    }
  ])
  ->toArray();

  // ViewにセットしてView側のセレクトボックスのoptionsにセット
  $this->set('userList', $userList);
<?php
  // usersモデルのEntity側(Model/Entity/User.php)
  protected function _getUserFullName() {
     // Entity側で苗字と名前を連結させて返す
     return $this->_properties['family_name'].$this->_properties['first_name'];
  }


のような感じで書くとキレイにカラムが連結された状態でセレクトボックスにセットされます。
functionとかなんなの。イミワカンナイという方は、以下のような書き方でもいけます。

<?php
  // これもusersモデルを使うということにします
  $userModel = TableRegistry::get('Users');
  $userList = $userModel->find('list'), [
    'keyField' => 'id',
    'valueField' => function ($entity) {
       return $entity['family_name'].$entity['first_name'];
    }
  ])
  ->toArray();

  // ViewにセットしてView側のセレクトボックスのoptionsにセット
  $this->set('userList', $userList);


ちなみに自分はイミワカンナイ派です。
お好きな方を使うといいんじゃないかなと思います。

ConoHaのLAMPイメージでサーバ構築してみた 〜Composerインストール〜

GitLabサーバが構築できた。
LAMPイメージから作ったサーバのPHPバージョンアップができた。
LAMPイメージから作ったサーバのMariaDBバージョンアップができた。


もうこれ、インフラもできるんじゃないですかね?
すっごーいとか言っていたら、きっと本職の方に石を投げられてしまいます。


さて、CakePHP3を使うので「あ。Composerもいるんじゃないの?」と気がつきました。
まぁ当然のごとく、インストールされていません。

仕方がないのでインストールします。

Composerのインストー

非常に簡単です。
公式ページを見て、その通りにコマンドを打てば終わりです。

getcomposer.org


上記のページを開くとインストールする時のコマンドが記載されています。
この4つのコマンド(2017年7月現在)を、上から順番に実行するだけです。
インストールする時は /usr/local/src/ 辺りに移動した方がいいかもしれません。

composer.pharを移動する

インストールが終わったら「composer.phar」を移動させましょう。
移動するのはパスを通すためです。

mv composer.phar /usr/local/bin/composer

Composerのバージョン確認

パスを通していれば以下のコマンドで確認できます。

composer -v

Composerの詳細が出てくれば無事にインストール完了です。
実行するとわかりますが、「rootで実行すんなよ!」と表示されます。
何かそうらしいです。

Composerのインストールは簡単でした。
どこかの偉い人、ありがとうございます。

ConoHaのLAMPイメージでサーバ構築してみた 〜MariaDBアップデート〜

前回はPHP5.6からPHP7にアップデートしました。
いい気になって、MariaDBのアップデートもしてみることにします。
どうでもいいんですが、自分は調子に乗りやすいB型です。

MariaDBの最新レポジトリを確認する

構築したLinuxのOSに応じてレポジトリが異なりますので、以下から確認します。

MariaDB - Setting up MariaDB Repositories - MariaDB


自分の場合はCentOS7なので、画面上からポチポチ選択しました。

1.Choose a Distro → LinuxのOS選択
2.Choose a Release → Linuxのバージョン選択
3.Choose a Version → インストールするMariaDBのバージョン選択

上記3つを選択すると、画面上に「yumでアップデートする時はこれ使ってね」的なものが表示されます。
自分の場合だと、以下のような記載でした。

Here is your custom MariaDB YUM repository entry for CentOS. 
Copy and paste it into a file under /etc/yum.repos.d/ (we suggest naming the file MariaDB.repo or something similar).


英語はよくわかりませんが、「/etc/yum.repos.d/にMariaDB.repoってファイル作ってね」ぽいです。

MariaDB.repoを作成して画面の内容を貼り付ける

ただ作るだけです。

vim /etc/yum.repos.d/MariaDB.repo

貼り付ける内容は前項で表示されている内容です。
こんな感じかと思います(CentOS7の64bit + MariaDB10.2の場合)

# MariaDB 10.2 CentOS repository list - created 2017-07-09 10:57 UTC
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.2/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1


よくわかりませんが、そのまま貼っておきました。

MariaDBを一旦停止する

そりゃアップデートするわけですから、止めるのも納得です。
以下のコマンドでMariaDBを停止できますので、停止してしまいましょう。

systemctl stop mariadb.service


多分止まってます。
エラーが出ていなければ。

yumMariaDBをアップデート

さて、ついにアップデートです。
yumMariaDBのアップデートを実行すると、先ほど作成したであろうMariaDB.repoの情報に基づいてアップデートされます。
上手くいくことを信じてコマンドを実行してみます。

yum update mariadb-server -y

コマンド自体は先ほどの画面上に表示されていると思います。
クライアントは不要かなと思ったので、サーバだけ入れてみました。

バージョンを確認

これできっとバージョンアップできているはずなので、コマンドを叩いて確認です。

mysql —version

mysql  Ver 15.1 Distrib 10.2.6-MariaDB, for Linux (x86_64) using readline 5.1

で き て い る !


PHPのアップデートに引き続き、MariaDBのアップデートもできました。
似非インフラエンジニアスキルがさらに加速しました。