20190506のPHPに関する記事は17件です。

phpの基本を学んでみよう4(オブジェクト指向の基本-クラスとプロパティの紐付け)

phpの基本を学んでみようシリーズ3
今回はオブジェクト指向のクラスとプロパティの紐付けまでです。

著者略歴

名前:YUUKI
ポートフォリオサイト:Pooks
現在:Webエンジニア見習い

クエリ情報

  • リンク先に情報を渡す
  • URL末尾の「?」以降に「キー名=値」の形で情報を送る
  • 「$_GET['キー名']」で値を受け取る

書き方

// 値を送る
<a href="show.php?name=CURRY">
  CURRY
</a>

// 値を受け取る
$_GET['name'];

都度更新

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

phpの基本を学んでみよう3(オブジェクト指向の基本-継承)

phpの基本を学んでみようシリーズ3
今回はオブジェクト指向の基本-継承までです。

著者略歴

名前:YUUKI
ポートフォリオサイト:Pooks
現在:Webエンジニア見習い

クラスプロパティ

  • メニュー数=インスタンス数
  • インスタンスを「クラス全体で管理」する際に用いる
  • クラスが持つデータ=クラスプロパティ
  • 「static」で定義
  • 「クラス名::クラスプロパティ名」でアクセス

書き方

<?php
  class Menu{
    // クラスプロパティの定義
    public static $count = 4;
  }

  // クラスプロパティにアクセス
  echo Menu::$count;
?>

コンストラクタの活用

  • 生成されたインスタンス数が変化した時に自動的に$countの値が変わるようにする
<?php
  class Menu{
    public static $count = 0;
    public function __construct(){
      self::$count++;
    }
  }

// インスタンスが生成される度に$countの値を増やす
$curry = new Menu('CURRY');
$pasta = new Menu('PASTA');
?>

self

  • クラス内でクラスプロパティにアクセスする際に使用
  • クラス自身のことを指す
  • 「self::$クラスプロパティ名」のように使う
<?php
  class Menu{
    public static $count = 0;
    public function __construct();
      // Menuクラスにアクセス
      self::$count++;
    }
  }

$menu1 = new Menu('CURRY');
$menu2 = new Menu('PASTA');

echo Menu::$count;
?>

クラスメソッド

  • static を用いて定義し、「クラス名::クラスメソッド名」のように呼び出す
  • 個々のインスタンスのデータに関係ない処理を行いたい時に用いる

使い方

<?php
  class Menu{
    private static $count = 0;
    // クラスメソッドの定義
    public static function getCount(){
      return self::$count;
    }
  }

  $menu1 = new Menu('CURRY');
  $menu2 = new Menu('PASTA');

  // クラスメソッドの呼び出し
  echo Menu::getCount();
?>

継承

  • すでに定義されているクラスのプロパティやメソッドを別のクラスに引き継ぐこと
  • 親クラス
    • 子クラス
    • 子クラスは、親クラスのプロパティやメソッドを全て引き継いだ上で「独自の機能」を追加できる。

書き方

<?php
  // 親クラス
  class 親クラス名{
  }

  // 子クラス
  class 子クラス extends 親クラス名{
  }
?>

独自プロパティ、独自メソッド

  • 子クラスに独自のプロパティを追加すること
  • 子クラスで定義したプロパティやメソッドは親クラスで呼び出せない

メソッドの呼び出し

  • 子クラスのメソッド呼び出し時には、メソッドが検索される
  • 子クラスにメソッドが定義されている場合には子クラスが、そのメソッドが定義されていない場合には親クラスのメソッドが定義される。

instanceof

  • あるインスタンスが特定のクラスのインスタンスであるか「判別」することができる
    • インスタンスが指定したクラスのインスタンス
      • true
    • そうでない
      • false

書き方

<?php
  $coffee = new Drink('COFFEE');
  $curry = new Food('CURRY');

  // $coffeeDrinkクラスのインスタンスであるかどうか
  if($coffee instanceof Drink){
    echo $coffee->getName();  // trueなのでgetNameが呼び出される
  }

  // $curryはDrinkクラスのインスタンスであるかどうか
  if($curry instanceof Drink){
    echo $curry->getName(); // falseなのでgetNameは呼び出されない
  }
?>

オーバーライド

  • メソッドの上書きのこと
  • 親クラスで定義されている同じ名前のメソッドを子クラスで再定義し、上書きする
<?php
  class Drink extends Menu{
    private $type;
    // 親クラスで定義されているconstructをオーバーライドする
    public function __construct($name,$type){
      $this->name = $name;
      $this->type = $type;
    }
  }

  // 子クラスからの呼び出し(オーバーライド)
  $coffee = new Drink('COFFEE');
?>

子クラスとアクセス権

  • privateプロパティは「子クラス」からもアクセスできない

// 親クラス
class Menu{
  private $name;
}

// 子クラス
class Drink extends Menu{
   public function __construct($name){
   // privateなのでアクセスできない
   $this->name = $name;
   }
}

protected

  • 子クラスから親クラスで定義したプロパティにアクセスする為に使う
  • アクセス権がprotectedの場合
    • クラス内
    • クラスを継承している子クラス

からのみアクセス可能

アクセス権まとめ

アクセス権 public protected private
クラス内
子クラス内 ×
クラス、子クラス外 × ×

parent

  • オーバーライドの際に親クラスで定義したメソッドを呼び出したいときに用いる
  • 「parent::メソッド名」のようにする
  • 「parent::メソッド名」を記述した場所で、親クラスのメソッドが実行される

書き方

<?php
// 親クラス
  class Menu{
    private $name;

    public function __construct($name, $price, $image){
      $this->name = $name;
    }

// 子クラス
  class Drink extends Menu{
  public function __construct($name, $price, $image, $type){
    // 親クラスの「__constructメソッド」を呼び出す
    parent::__construct($name, $price, $image);
    $this->type = $type;
  }
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPとJavaのコンストラクタを比べてみた

PHP と Java のコンストラクタを比較してみたいと思います。

要約

  • Java
    • 「暗黙的コンストラクタ呼び出し」が発生する場合がある
  • PHP
    • 「暗黙的コンストラクタ呼び出し」は発生しない
    • サブクラスでコンストラクタが 定義されている 場合
      • 「サブクラスのコンストラクタ」のみ が呼び出される
      • 「スーパークラスのコンストラクタ」を呼び出す場合は parent::__construct() を呼び出す
    • サブクラスでコンストラクタが 定義されていない 場合
      • 「スーパークラスのコンストラクタ」 が呼び出される

動作確認環境

  • Java
    • OpenJDK 11.0.1
  • PHP
    • 7.3.5
  • Ruby
    • 2.6.3p62

はじめに

Java 出身の人向けに、以下のように説明している記事を見かけます。

コンストラクタを定義するとき、Java ではクラス名と同名のメソッドとして定義する。
一方、PHP では __construct() で定義する。
以上。

class Sample
{
  public Sample() {
    System.out.println("Constructor of Sample Class");
  }
}
class Sample
{
  public function __construct() {
    echo "Constructor of Sample Class\n");
  }
}

でも、PHP と Java の違いって、本当にそれだけですかね?1

PHPに「暗黙的コンストラクタ呼び出し」はない

PHP と Java で違いが生じるのは 継承 を使ったときです。

Java

Java では、暗黙的に「スーパークラスのコンストラクタ」が呼び出されることがあります。2

public class SuperClass {
    public SuperClass() {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    public SubClass() {
        System.out.println("Constructor of Sub Class");
    }
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}

上記コードを実行すると、以下のように表示されます。

Constructor of Super Class
Constructor of Sub Class

「サブクラスのコンストラクタ」には、「スーパークラスのコンストラクタ」を明示的に呼び出す処理( super() )は書かれていません。

しかし、このような場合、Java は「 引数を持たない スーパークラスのコンストラクタ」を 暗黙的に 呼び出すという 言語仕様 になっています。3 ここで 引数を持たない と書いてあるのが重要です。

コンストラクタ本体が明示的コンストラクタ呼出しで開始されず,宣言しているコンストラクタが根源的クラスであるクラスObjectの一部でなければ,コンストラクタ本体は,コンパイラにより暗黙に,上位クラスのコンストラクタ呼出し"super();"で開始すると仮定される。すなわち,その直接的上位クラスに存在する実引数を取らないコンストラクタ呼出しとする。

先程のコードの場合、「サブクラスのコンストラクタ」に「スーパークラスのコンストラクタ」を明示的に呼び出す処理が書かれていなかったため、暗黙的に引数を持たない スーパークラスのコンストラクタ」が呼び出されました。

コンパイルエラー

一方、「スーパークラスのコンストラクタ」に 「 引数を持つ コンストラクタ」のみが定義されている場合、「 引数を持たない コンストラクタ」を呼び出すことができないため、コンパイル時にエラーになります。

public class SuperClass {
    // 引数を持つコンストラクタのみが定義されている
    public SuperClass(String name) {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    public SubClass() {
        System.out.println("Constructor of Sub Class");
    }
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}
Error:(4, 23) java: クラス com.example.SuperClassのコンストラクタ SuperClassは指定された型に適用できません。
  期待値: java.lang.String
  検出値: 引数がありません
  理由: 実引数リストと仮引数リストの長さが異なります

この場合は、「サブクラスのコンストラクタ」で、明示的に 「スーパークラスのコンストラクタ」を呼び出す必要があります。

public class SuperClass {
    // 引数を持つコンストラクタのみが定義されている
    public SuperClass(String name) {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    public SubClass() {
        // スーパークラスのコンストラクタを明示的に呼び出す
        super("hoge");
        System.out.println("Constructor of Sub Class");
    }
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}

デフォルトコンストラクタ

また、サブクラスにコンストラクタが定義されていない場合も、暗黙的に「 引数を持たない スーパークラスのコンストラクタ」が呼び出されることがあります。

public class SuperClass {
    public SuperClass() {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    // サブクラスのコンストラクタが定義されていない
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}

上記コードを実行すると、以下のように表示されます。

Constructor of Super Class

サブクラスにコンストラクタが定義されていない場合、Java は自動的に デフォルトコンストラクタ を作成します。
そして、デフォルトコンストラクタは「 引数を持たない スーパークラスのコンストラクタ」を呼び出す 言語仕様 になっています。

クラスがコンストラクタ宣言を含んでいなければ,実引数を取らないデフォルトコンストラクタ(default constructor)が自動的に提供される。

  • 宣言しているクラスが,ルートクラスであるクラスObjectならば,デフォルトコンストラクタは,空の本体をもつ。
  • そうでなければ,デフォルトコンストラクタは,実引数を取らず,実引数をもたない上位クラスのコンストラクタを単に呼び出す。

コンパイラがデフォルトコンストラクタを提供しているが,上位クラスが実引数をもたないコンストラクタをもっていなければ,コンパイル時エラーが発生する。

継承階層

なお、継承階層が深いコンストラクタから順番に呼び出されます。

public class AncestorClass {
    public AncestorClass() {
        System.out.println("Constructor of Ancestor Class");
    }
}

public class SuperClass extends AncestorClass {
    public SuperClass() {
        System.out.println("Constructor of Super Class");
    }
}

public class SubClass extends SuperClass {
    public SubClass() {
        System.out.println("Constructor of Sub Class");
    }
}

public class Main {
    public static void main(String[] args) {
        new SubClass();
    }
}

上記コードを実行すると、以下のように継承階層が深い順に実行されます。

  • AncestorClass のコンストラクタ
  • SuperClass のコンストラクタ
  • SubClass のコンストラクタ
Constructor of Ancestor Class
Constructor of Super Class
Constructor of Sub Class

PHP

一方、PHP では暗黙的に「スーパークラスのコンストラクタ」が呼び出されることはありません。
以下は、公式マニュアル からの引用です。

注意: 子クラスがコンストラクタを有している場合、親クラスのコンストラクタが 暗黙の内にコールされることはありません。 親クラスのコンストラクタを実行するには、子クラスのコンストラクタの 中で parent::__construct() をコールすることが 必要です。 子クラスでコンストラクタを定義していない場合は、親クラスのコンストラクタを継承します (ただし、private 宣言されている場合は除く)。 これは、通常のクラスメソッドと同様です。

PHP では、コンストラクタの継承は、通常のメソッドのオーバーライドと同様に考えることができます。
つまり、サブクラスでコンストラクタを定義した場合は、スーパークラスのコンストラクタをオーバーライドしたことになるのです。

したがって、PHP では、以下のように単純なルールにすることができます。

ルール

  • サブクラスでコンストラクタが 定義されている 場合
    • 「サブクラスのコンストラクタ」のみ が呼び出される
    • 「スーパークラスのコンストラクタ」を呼び出す場合は parent::__construct() を呼び出す
  • サブクラスでコンストラクタが 定義されていない 場合
    • 「スーパークラスのコンストラクタ」 が呼び出される

サブクラスでコンストラクタが定義されている場合

サブクラスでコンストラクタが 定義されている 場合は、「サブクラスのコンストラクタ」のみ が呼び出されます。
暗黙的に「スーパークラスのコンストラクタ」が呼び出されることはありません。

class SuperClass {
  public function __construct() {
    echo "Constructor of Super Class\n";
  }
}

class SubClass extends SuperClass {
  public function __construct() {
    echo "Constructor of Sub Class\n";
  }
}

new SubClass();
Constructor of Sub Class

また、通常のメソッドのオーバーライドと異なり、コンストラクタの場合はシグネチャが異なっていても例外的にオーバーライドと見なされます(公式マニュアル)。

メソッドをオーバーライドするときには、パラメータのシグネチャも同じでなければなりません。 もし違っていれば、PHP は E_STRICT レベルのエラーとなります。ただしコンストラクタは例外で、 異なるパラメータでオーバーライドすることができます。

要するに、 サブクラスでコンストラクタが定義されている場合は、常に「サブクラスのコンストラクタ」のみが呼び出される ということですね。

class SuperClass {
  public function __construct() {
    echo "Constructor of Super Class\n";
  }
}

class SubClass2 extends SuperClass {
  // スーパークラスのコンストラクタにはない引数がある
  public function __construct(String $name) {
    echo "Constructor of Sub Class\n";
  }
}

new SubClass2('hoge');
Constructor of Sub Class

サブクラスでコンストラクタが定義されていない場合

サブクラスでコンストラクタが 定義されていない 場合は、「スーパークラスのコンストラクタ」 が呼び出されます。

class SuperClass {
  public function __construct() {
    echo "Constructor of Super Class\n";
  }
}

class SubClass3 extends SuperClass {}

new SubClass3();
Constructor of Super Class

スーパークラスのコンストラクタを呼び出す

「サブクラスのコンストラクタ」から「スーパークラスのコンストラクタ」を呼び出す場合は、 parent::__construct() を呼び出します。

parent::__construct() は Java の super() に相当するものですが、いろいろと制約がある super() に比べると、はるかに制約のないものです。

class SuperClass {
  public function __construct() {
    echo "Constructor of Super Class\n";
  }
}

class SubClass4 extends SuperClass {
    public function __construct() {
    // 先頭行でなくてもいい
    echo "Constructor of Sub Class Called\n";
    // 何度呼び出してもいい
        parent::__construct();
        parent::__construct();
    }
}

new SubClass4();

parent::__construct() を 2 回呼び出したので、 "Constructor of Super Class" が 2 行表示されていることに注意してください。

Constructor of Sub Class Called
Constructor of Super Class
Constructor of Super Class

なお、PHP では 暗黙的に 「スーパークラスのコンストラクタ」を呼び出さないため、 自動的に 継承階層が深いコンストラクタから順番に呼び出すこともありません。
そうしたい場合は、それぞれのクラスで明示的に parent::construct() を呼び出す必要があります。

おまけ: Ruby

「そんな変な仕様になってるのは PHP のオブジェクト指向が貧弱だからだろ?」とか言われると心外なので、生まれながらのオブジェクト指向言語である Ruby でも試してみました。

詳しくは書きませんが、PHP と同様の動作をしていることが分かると思います。

# スーパークラス
class SuperClass
    def initialize
        puts 'initialize of Super Class'
    end
end

# サブクラス
# サブクラスのコンストラクタのみが呼び出される
class SubClass < SuperClass
    def initialize
        puts 'initialize of Sub Class'
    end
end

# サブクラス2
# サブクラスのコンストラクタのみが呼び出される
class SubClass2 < SuperClass
  # スーパークラスのコンストラクタにはない引数がある
    def initialize name
        puts 'initialize of Sub Class'
    end
end

# サブクラス3
# コンストラクタを定義していない場合
# スーパークラスのコンストラクタが呼び出される
class SubClass3 < SuperClass
end

# サブクラス4
# superメソッドを呼び出した場合
# スーパークラスの同名メソッド(initialize)が呼び出される
class SubClass4 < SuperClass
  def initialize
    super
  end
end

puts 'SubClass'
SubClass.new
puts 'SubClass2'
SubClass2.new('name')
puts 'SubClass3'
SubClass3.new
puts 'SubClass4'
SubClass4.new
SubClass
initialize of Sub Class
SubClass2
initialize of Sub Class
SubClass3
initialize of Super Class
SubClass4
initialize of Super Class

参考

Java

PHP

Ruby

雑感

個人的好みだけで言うと、勝手に後ろで動かれるのは好きではないため、PHP や Ruby のほうが好みです。4

ただ、 継承is-a 関係 であるべきことを考えると、「暗黙的コンストラクタ呼び出し」を行わない PHP や Ruby では、サブクラスが全く関係ないスーパークラスを継承できてしまうようにも思います。

そういう観点で考えると、「暗黙的コンストラクタ呼び出し」はむしろそうなるのが自然で、そうなって問題が起きるようなプログラムはそもそもオブジェクト指向的でないということになるのかなとも思います。


  1. PHP 4 ではじめてオブジェクト指向を取り入れたときは、PHP でも、コンストラクタはクラス名と同名のメソッドでした。その後、PHP 5 で __construct() と書くようになりましたが、PHP 4 形式で書いてもコンストラクタとして認識されました。しかし、PHP 7 では、PHP 4 形式で書いた場合に「E_DEPRECATED」エラーが出力されるようになりました。そして、今後リリースされる PHP 8 からは、コンストラクタとして認識しなくなる予定です。詳しくは RFC を参照のこと。 

  2. Java だけでなく C++ でも同様だと思います。 

  3. 引用した言語仕様は、やや古い第 2 版ですが、最新の 言語仕様 でもほぼ同じ記載になっています。 

  4. 最初に触れたオブジェクト指向言語(?)が PHP だったからというのもあるかもしれません。というか、それに尽きるか。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PDOを使ってデータベースに接続する方法について解説!

はじめに

  • データベースに接続する方法について本やネットの情報から調べて理解したことをまとめました。
  • もし、書いていることに何か間違いがある場合はご指摘いただけると嬉しいです。

接続するデータベースとテーブル

  • 以下のようなデータベースとテーブルがあると仮定して実行します。
  • PDOを利用してデータベースに接続します。

データベース名: test_db1
テーブル名: user

name email
てすと太郎 test-taro@test.test

PDOとは

  • 「PHP Data Objects」の略で、PHPからデータベースへ接続するためのクラスのことです。

データベース(MySQL)に接続する方法

  • データベースに接続するには、どこにある何というデータベースにどのユーザーが接続するのか情報を記述します。
  • PDOクラスをインスタンス化するときに、引数にデータベースの接続に必要な情報を記述することで接続できます。

書き方

インスタンス名 = new PDO("データベースの種類:host=接続先アドレス, dbname=データベース名,charset=文字エンコード" "ユーザー名", "パスワード", オプション)

引数

引数について解説します。

データベースの種類

  • 使用したいデータベースを指定

ホスト名(host)

  • 接続先アドレス

データベース名(dbname)

  • 使用したいデータベース名

文字コード(charset)

  • 文字コード(utf8)

ユーザー名

  • データベースにログインするユーザー名

パスワード

  • データベースにログインするパスワード

オプション

  • オプションは連想配列で指定します。
  • オプションはデータベースに接続する時に様々な機能を使うことができます。

コード例

$user = "ここにユーザー名が入ります";
$password = "ここにパスワードが入ります";

$dbh = new PDO("mysql:host=localhost; dbname=test_db1; charset=utf8", "$user", "$password");
  • 作成したインスタンスを$dbhという変数に代入しています。
  • dbhはデータベースハンドラの略です。

データベースに接続した後にオプションを指定する方法

  • データベースに接続した後にオプションを指定するには PDO::setAttributeメソッドを使用します。

書き方

$dbh->setAttribute(属性 , );
  • PDO::setAttributeメソッドは属性をセットするメソッドです。
  • 第1引数に属性を、第2引数に値を指定します。

コード例

$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)とは

  • PDO::ATTR_ERRMODEという属性でPDO::ERRMODE_EXCEPTIONの値を設定することでエラーが発生したときに、PDOExceptionの例外を投げてくれます。

データを取得する

  • ここまでデータベース接続ができたので、次からデータを取得する方法について紹介します。

コード例

$stmt = $dbh->query('SELECT * FROM user');
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

PDO::queryメソッドとは

$stmt = $dbh->query('SELECT * FROM user');
  • PDO::queryメソッドを実行するとクエリを実行します。

PDOStatement::fetchAllメソッドとは

$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
  • PDOStatement::fetchAllメソッドは該当する全てのデータを配列として取得します。
  • FETCH_ASSOCでカラム名をキーとする連想配列で返します。

データベースの接続を閉じる方法

  • データベースの接続を閉じるには$dbh = null;を使います。

データベースに接続するコード例

コード例
<?php

$user = "ここにユーザー名が入ります";
$password = "ここにパスワードが入ります";

$dbh = new PDO("mysql:host=localhost; dbname=test_db1; charset=utf8", "$user", "$password");
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $stmt = $dbh->query('SELECT * FROM user');
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

    var_dump($result);

    $dbh = null;

?>
実行結果
array(1) { [0]=> array(2) { ["name"]=> string(15) "てすと太郎" ["email"]=> string(19) "test-taro@test.test" } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PDOを使ってデータベースに接続する方法を解説!

はじめに

  • データベースに接続する方法について本やネットの情報から調べて理解したことをまとめました。
  • もし、書いていることに何か間違いがある場合はご指摘いただけると嬉しいです。

接続するデータベースとテーブル

  • 以下のようなデータベースとテーブルがあると仮定して実行します。
  • PDOを利用してデータベースに接続します。

データベース名: test_db1
テーブル名: user

name email
てすと太郎 test-taro@test.test

PDOとは

  • 「PHP Data Objects」の略で、PHPからデータベースへ接続するためのクラスのことです。

データベース(MySQL)に接続する方法

  • データベースに接続するには、どこにある何というデータベースにどのユーザーが接続するのか情報を記述します。
  • PDOクラスをインスタンス化するときに、引数にデータベースの接続に必要な情報を記述することで接続できます。

書き方

インスタンス名 = new PDO("データベースの種類:host=接続先アドレス, dbname=データベース名,charset=文字エンコード" "ユーザー名", "パスワード", オプション)

引数

引数について解説します。

データベースの種類

  • 使用したいデータベースを指定

ホスト名(host)

  • 接続先アドレス

データベース名(dbname)

  • 使用したいデータベース名

文字コード(charset)

  • 文字コード(utf8)

ユーザー名

  • データベースにログインするユーザー名

パスワード

  • データベースにログインするパスワード

オプション

  • オプションは連想配列で指定します。
  • オプションはデータベースに接続する時に様々な機能を使うことができます。

コード例

$user = "ここにユーザー名が入ります";
$password = "ここにパスワードが入ります";

$dbh = new PDO("mysql:host=localhost; dbname=test_db1; charset=utf8", "$user", "$password");
  • 作成したインスタンスを$dbhという変数に代入しています。
  • dbhはデータベースハンドラの略です。

データベースに接続した後にオプションを指定する方法

  • データベースに接続した後にオプションを指定するには PDO::setAttributeメソッドを使用します。

書き方

$dbh->setAttribute(属性 , );
  • PDO::setAttributeメソッドは属性をセットするメソッドです。
  • 第1引数に属性を、第2引数に値を指定します。

コード例

$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)とは

  • PDO::ATTR_ERRMODEという属性でPDO::ERRMODE_EXCEPTIONの値を設定することでエラーが発生したときに、PDOExceptionの例外を投げてくれます。

データを取得する

  • ここまでデータベース接続ができたので、次からデータを取得する方法について紹介します。

コード例

$stmt = $dbh->query('SELECT * FROM user');
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

PDO::queryメソッドとは

$stmt = $dbh->query('SELECT * FROM user');
  • PDO::queryメソッドを実行するとクエリを実行します。

PDOStatement::fetchAllメソッドとは

$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
  • PDOStatement::fetchAllメソッドは該当する全てのデータを配列として取得します。
  • FETCH_ASSOCでカラム名をキーとする連想配列で返します。

データベースの接続を閉じる方法

  • データベースの接続を閉じるには$dbh = null;を使います。

データベースに接続するコード例

コード例
<?php

$user = "ここにユーザー名が入ります";
$password = "ここにパスワードが入ります";

$dbh = new PDO("mysql:host=localhost; dbname=test_db1; charset=utf8", "$user", "$password");
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $stmt = $dbh->query('SELECT * FROM user');
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

    var_dump($result);

    $dbh = null;

実行結果
array(1) { [0]=> array(2) { ["name"]=> string(15) "てすと太郎" ["email"]=> string(19) "test-taro@test.test" } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データベースに接続する方法について解説!

はじめに

  • データベースに接続する方法について本やネットの情報から調べて理解したことをまとめました。
  • もし、書いていることに何か間違いがある場合はご指摘いただけると嬉しいです。

接続するデータベースとテーブル

  • 以下のようなデータベースとテーブルがあると仮定して実行します。
  • PDOを利用してデータベースに接続します。

データベース名: test_db1
テーブル名: user

name email
てすと太郎 test-taro@test.test

PDOとは

  • 「PHP Data Objects」の略で、PHPからデータベースへ接続するためのクラスのことです。

データベース(MySQL)に接続する方法

  • データベースに接続するには、どこにある何というデータベースにどのユーザーが接続するのか情報を記述します。
  • PDOクラスをインスタンス化するときに、引数にデータベースの接続に必要な情報を記述することで接続できます。

書き方

インスタンス名 = new PDO("データベースの種類:host=接続先アドレス, dbname=データベース名,charset=文字エンコード" "ユーザー名", "パスワード", オプション)

引数

引数について解説します。

データベースの種類

  • 使用したいデータベースを指定

ホスト名(host)

  • 接続先アドレス

データベース名(dbname)

  • 使用したいデータベース名

文字コード(charset)

  • 文字コード(utf8)

ユーザー名

  • データベースにログインするユーザー名

パスワード

  • データベースにログインするパスワード

オプション

  • オプションは連想配列で指定します。
  • オプションはデータベースに接続する時に様々な機能を使うことができます。

コード例

$user = "ここにユーザー名が入ります";
$password = "ここにパスワードが入ります";

$dbh = new PDO("mysql:host=localhost; dbname=test_db1; charset=utf8", "$user", "$password");
  • 作成したインスタンスを$dbhという変数に代入しています。
  • dbhはデータベースハンドラの略です。

データベースに接続した後にオプションを指定する方法

  • データベースに接続した後にオプションを指定するには PDO::setAttributeメソッドを使用します。

書き方

$dbh->setAttribute(属性 , );
  • PDO::setAttributeメソッドは属性をセットするメソッドです。
  • 第1引数に属性を、第2引数に値を指定します。

コード例

$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)とは

  • PDO::ATTR_ERRMODEという属性でPDO::ERRMODE_EXCEPTIONの値を設定することでエラーが発生したときに、PDOExceptionの例外を投げてくれます。

データを取得する

  • ここまでデータベース接続ができたので、次からデータを取得する方法について紹介します。

コード例

$stmt = $dbh->query('SELECT * FROM user');
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

PDO::queryメソッドとは

$stmt = $dbh->query('SELECT * FROM user');
  • PDO::queryメソッドを実行するとクエリを実行します。

PDOStatement::fetchAllメソッドとは

$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
  • PDOStatement::fetchAllメソッドは該当する全てのデータを配列として取得します。
  • FETCH_ASSOCでカラム名をキーとする連想配列で返します。

データベースの接続を閉じる方法

  • データベースの接続を閉じるには$dbh = null;を使います。

データベースに接続するコード例

コード例
<?php

$user = "ここにユーザー名が入ります";
$password = "ここにパスワードが入ります";

$dbh = new PDO("mysql:host=localhost; dbname=test_db1; charset=utf8", "$user", "$password");
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $stmt = $dbh->query('SELECT * FROM user');
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

    var_dump($result);

    $dbh = null;

?>
実行結果
array(1) { [0]=> array(2) { ["name"]=> string(15) "てすと太郎" ["email"]=> string(19) "test-taro@test.test" } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MYSQLのエラー関連のエラーってわかりにくいよね

エラー発生:SQLSTATE[HY093]

Invalid parameter number: number of bound variables does not match number of tokens

例外処理時のExceptionにてキャッチした際に発生したエラー。

catch(Exception $e){
debug('エラー発生:'.$e->getMessage());
}

debug()関数でエラー文をエラー時に別ファイルに作成

エラーの考えられる原因

1.SQL文のミス
主にINSERT INTOの対象となるパラメーター以外の記述はしてはいけない(と思う)
対象プレースホルダーに注意。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelのRelationshipsを取得する際の注意点(selectで外部キーも指定しないとnull)

LaravelでCityモデルにPrefectureモデルへの関連が定義されているとします。

<?php
declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class City extends Model
{
    // 略

    public function prefecture()
    {
        return $this->belongsTo('App\Models\Prefecture')->open();
    }
}

以下のように、データを取得した後で関連データを参照すると、参照のたびにSQLが発行されます。
N+1問題というやつですね。

$cities = City::get();
foreach($cities as $city) {
    var_dump($city->prefecture);
}

対応として、Eager Loadingと呼ばれる事前データ取得処理をおこないます。
Laravelではwithメソッドでモデルを指定します。

$cities = City::with('Prefecture')->get();
foreach($cities as $city) {
    var_dump($city->prefecture);
}

// 複数の場合は配列もしくは複数回withメソッドを呼び出します。
$cities = City::with(['Prefecture', 'Towns'])->get();
$cities = City::with('Prefecture')->with('Towns')->get();

さらに、不要なカラムはいらないので、selectメソッドを追加してみます。

$cities = City::select('code')->with('Prefecture')->get();
foreach($cities as $city) {
    var_dump($city->prefecture);
}

ぎゃー

NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL (略)

外部キーも指定しないとダメなようです

$cities = City::select(['code', 'prefecture_id'])->with('Prefecture')->get();
foreach($cities as $city) {
    var_dump($city->prefecture);
}

ということで、関連テーブルをEagar Lodingで取得する際にselectメソッドを組み合わせる場合は、外部キーもselectに指定する必要があるという覚書でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【その2】メッセージフォーマットをFlex Messageで綺麗にカスタマイズ 〜「LINE Messaging API」でヤフオク検索ボットをつくる〜

LINE Messaging API 開発。今回は第2弾ということでFlex Messageに挑戦します。

(前回)「LINE Messaging API」でヤフオク検索ボットを作ったら結構便利だった(PHP)

さっそく今回の完成品をお披露目します。
May-06-2019 17-06-36.gif

前回は吹き出しの中にただテキストを入れているだけでしたが、Flex Message によってレイアウトが構成され、より見やすい内容となりました。

  • 商品画像
  • 価格
  • 入札状況
  • 残り時間
  • タップしたらヤフオク商品へのリンク

等を Flex Message を利用し実装しました。

Flex Message とは

複数の要素を組み合わせてレイアウトを自由にカスタマイズできるメッセージ

  • ボックスレイアウトシステムを使って水平・垂直のレイアウトを組み合わせ、複雑なレイアウトを構築できます。
  • カルーセル形式でメッセージを表示できます。ユーザーは複数のメッセージバブルをスクロールして閲覧できます。
  • 書字方向を左横書きまたは右横書きに指定できます。
  • 実際に開発を始める前に、Simulatorを使って表示をテストできます。

Simulator

Simulator のおかげで開発は捗りましたので、実際に開発される方はぜひご利用ください。

実装

webhook.php
<?php

// PHP Simple HTML DOM Parser
require_once '../lib/simplehtmldom_1_8_1/simple_html_dom.php';

class Line {

    // コンストラクタでHTTPリクエストBodyをセット
    function __construct() {
        $this->request_body = file_get_contents('php://input');
    }

    // HTTP リクエスト Body
    private $request_body;

    // チャネル設定
    private $channel = [
        "secret"       => 'チャネルシークレット',
        "access_token" => 'チャネルアクセストークン(ロングターム)'
    ];

    // public function

    // リクエスト署名取得
    public function valid_signature() {
        $headers = getallheaders();
        $channel = $this->get_channel();
        $hash = hash_hmac('sha256', $this->request_body, $channel['secret'], true);
        $signature = base64_encode($hash);
        return $headers['X-Line-Signature'] === $signature;
    }

    // event オブジェクト抜き出し
    public function get_webhook_event() {
        $decode_http_request_body = json_decode($this->get_http_request_body());
        return $decode_http_request_body->{'events'}[0];
    }

    // Flex Message でメッセージ返信
    public function reply_flex($messages) {
        $event   = $this->get_webhook_event();
        $channel = $this->get_channel();

        // リクエストヘッダ
        $http_header = [
            'Content-Type: application/json',
            "Authorization: Bearer {$channel['access_token']}",
        ];

        $flex_message = [
            "replyToken" => $event->{'replyToken'},
            "messages"   => []
        ];

        $message_count = 0;
        foreach ($messages as $message) {
            $message_count++;
            if ($message_count > 5) break;

            $flex_message['messages'][] = 
                [
                    "type"     => "flex",
                    "altText"  => "ヤフオク商品スクレイピングBot",
                    "contents" => [
                        "type" => "bubble",
                        "hero" => [
                            "type"        => "image",
                            "url"         => $message['image'],
                            "size"        => "full",
                            "aspectRatio" => "20:13",
                            "aspectMode"  => "cover",
                            "action"      => [
                                "type" => "uri",
                                "uri"  => $message['href']
                            ]
                        ],
                        "body" => [
                            "type"    => "box",
                            "layout"  => "vertical",
                            "spacing" => "md",
                            "contents" => [
                                [
                                    "type" => "text",
                                    "text" => $message['title'],
                                    "wrap" => true,
                                ],
                                [
                                    "type"     => "box",
                                    "layout"   => "baseline",
                                    "margin"   => "md",
                                    "contents" => [
                                        [
                                            "type"   => "text",
                                            "text"   => $message['category'],
                                            "size"   => "xxs",
                                            "color"  => "#999999",
                                            "margin" => "md",
                                            "wrap"   => true,
                                            "flex"   => 0
                                        ]
                                    ]
                                ],
                                [
                                    "type"     => "box",
                                    "layout"   => "vertical",
                                    "margin"   => "lg",
                                    "spacing"  => "sm",
                                    "contents" => [
                                        [
                                            "type"     => "box",
                                            "layout"   => "baseline",
                                            "spacing"  => "sm",
                                            "contents" => [
                                                [
                                                    "type"  => "text",
                                                    "text"  => "現在",
                                                    "color" => "#aaaaaa",
                                                    "size"  => "sm",
                                                    "flex"  => 2
                                                ],
                                                [
                                                    "type"  => "text",
                                                    "text"  => $message['price']['now'],
                                                    "wrap"  => true,
                                                    "size"  => "sm",
                                                    "color" => "#666666",
                                                    "flex"  => 4
                                                ]
                                            ]
                                        ],
                                        [
                                            "type"     => "box",
                                            "layout"   => "baseline",
                                            "spacing"  => "sm",
                                            "contents" => [
                                                [
                                                    "type"  => "text",
                                                    "text"  => "即決",
                                                    "color" => "#aaaaaa",
                                                    "size"  => "sm",
                                                    "flex"  => 2
                                                ],
                                                [
                                                    "type"  => "text",
                                                    "text"  => $message['price']['prompt'],
                                                    "wrap"  => true,
                                                    "size"  => "sm",
                                                    "color" => "#666666",
                                                    "flex"  => 4
                                                ]
                                            ]
                                        ],
                                        [
                                            "type"     => "box",
                                            "layout"   => "baseline",
                                            "spacing"  => "sm",
                                            "contents" => [
                                                [
                                                    "type"  => "text",
                                                    "text"  => "入札",
                                                    "color" => "#aaaaaa",
                                                    "size"  => "sm",
                                                    "flex"  => 2
                                                ],
                                                [
                                                    "type"  => "text",
                                                    "text"  => $message['other']['bid'] . " 件",
                                                    "wrap"  => true,
                                                    "size"  => "sm",
                                                    "color" => "#666666",
                                                    "flex"  => 4
                                                ]
                                            ]
                                        ],
                                        [
                                            "type"     => "box",
                                            "layout"   => "baseline",
                                            "spacing"  => "sm",
                                            "contents" => [
                                                [
                                                    "type"  => "text",
                                                    "text"  => "残り時間",
                                                    "color" => "#aaaaaa",
                                                    "size"  => "sm",
                                                    "flex"  => 2
                                                ],
                                                [
                                                    "type"  => "text",
                                                    "text"  => $message['other']['limit'],
                                                    "wrap"  => true,
                                                    "size"  => "sm",
                                                    "color" => "#666666",
                                                    "flex"  => 4
                                                ]
                                            ]
                                        ]                                        
                                    ]
                                ]
                            ]
                        ] // body
                    ]
                ]; // messages array (1st)
        }

        // 送信
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:1.7.3) Gecko/20041001 Firefox/0.10.1");
        curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply');
        curl_setopt($ch, CURLOPT_HTTPHEADER, $http_header);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($flex_message));
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_ENCODING, "");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_AUTOREFERER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 10);

        $content  = curl_exec($ch);
        $response = curl_getinfo($ch);
        $errno    = curl_errno($ch);
        $error    = curl_error($ch);

        curl_close($ch);

        if (CURLE_OK !== $errno) {
            error_log(print_r($response, true));
            throw new RuntimeException($error, $errno);
        }

        error_log("Success!");
            error_log(print_r($content, true));
        error_log(print_r($response, true));


        return true;
    }    

    // private function

    private function get_channel() {
        return $this->channel;
    }

    private function get_http_request_body() {
        return $this->request_body;
    }
}

function main() {

    $line = new Line();

    // Webhook 署名検証
    if ($line->valid_signature() === false) {
        // 検証失敗
        error_log('[Failed] Invalid signature');
        return;
    }

    $event = $line->get_webhook_event();
    $reply_messages = get_yahuoku_items($event->{"message"}->{"text"});

    // ユーザへ返信へ
    $line->reply_flex($reply_messages);
}

function get_yahuoku_items($user_message) {

    // ユーザが投稿したメッセージを検索ワードとし検索
    $user_message_urlen = urlencode($user_message);

    $url_search   = "https://auctions.yahoo.co.jp/search/search?auccat=&tab_ex=commerce&ei=utf-8&aq=-1&oq=&sc_i=&fr=auc_top&p={$user_message_urlen}&x=0&y=0&fixed=0";
    $yahuoku_html = file_get_html($url_search);

    // 商品一覧を取得
    $yahuoku_html_items = $yahuoku_html->find('.Product');

    // 返信メッセージ用配列
    $reply_messages = [];

    foreach($yahuoku_html_items as $item) {

        // 1個目は商品じゃない項目のためスルー
        if ($item->find('.Product__detail .Product__titleLink') == false) continue;

        // 画像
        $image = $item->find('.Product__image .Product__imageData', 0);

        // タイトル
        $title = $item->find('.Product__detail .Product__titleLink', 0);

        // カテゴリ
        $category = 0;
        if ($categories = $item->find('.Product__detail .Product__categoryItems', 0)) {
            $category = $categories->plaintext;
            $category = preg_replace("/( +)/", " > ", $category);
        }

        // 価格
        $prices = $item->find('.Product__detail .Product__priceInfo .Product__price');
        $price_now    = 0;
        $price_prompt = '未設定';
        if ($prices[0]->find('.Product__priceValue')) {
            $price_now = $prices[0]->find('span.Product__priceValue', 0)->plaintext;
        }
        if ($prices[1]->find('.Product__priceValue')) {
            $price_prompt = $prices[1]->find('span.Product__priceValue', 0)->plaintext;
        }

        // 入札件数 残り時間
        $others = $item->find('.Product__detail .Product__otherInfo .u-floatL');
        $other_bid   = 0;
        $other_limit = 0;
        $other_bid   = $others[0]->find('.Product__bid', 0)->plaintext;
        $other_limit = $others[0]->find('.Product__time', 0)->plaintext;

        // 商品URL
        $href = $item->find('.Product__image a.Product__imageLink', 0)->href;

        // 溜める
        $reply_messages[] = [
            'image'    => $image->src,
            'category' => $category,
            'title'    => $title->plaintext,
            'price'    => [
                'now'    => $price_now,
                'prompt' => $price_prompt,
            ],
            'other' => [
                'bid'   => $other_bid,
                'limit' => $other_limit
            ],
            'href' => $href
        ];
    }

    $yahuoku_html->clear();

    return $reply_messages;
}

main();

?>

前回との違い

  • LineクラスにFlex Messageでメッセージ送信するためのeply_flex関数を作成
  • ヤフオク商品のスクレイピング処理が複雑になったため get_yahuoku_items 関数を作成

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CloudFormationでLaravel5.5が動く環境をEC2(Ubuntu18.04)上に作ったときのまとめ

GWだったので趣味で開発してるLaravelプロジェクトをEC2で動かすために必要な環境をCloudFormationで立ててみました。その時に必要となった知識をまとめておきます。

必要な環境

  • OS

    • Ubuntu18.04
  • 必要サーバ

    • EC2インスタンス1台
  • インストールが必要なもの(ざっくり)

    • php7.2
    • php-fpm
    • composer
    • Laravelの動作に必要なPHPのライブラリ群
    • nginx
  • 追加で必要なサービス

    • Elastic IP
  • Security Groupの要件

    • ssh接続の許可
    • http接続の許可

CloudFormationによるインスタンスの管理

CloudFormationには初期化コマンドを実行するための仕組みをpythonスクリプトで提供していますが、Ubuntu18.04にはデフォルトで備えられていないので自前インストールします。pipを使って下記のコマンドで実現できました。

sudo apt-get update
sudo apt-get -y install python-pip
pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz
sudo cp -a /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup
chmod u+x /etc/init.d/cfn-hup
sudo update-rc.d cfn-hup defaults
sudo service cfn-hup start

これにより

  • テンプレート内でパッケージインストールなどをパラメータで指定して実行(cfn-init)
  • cfn-initで実行したコマンドのステータスを見て通達(cfn-signal)
  • テンプレート更新によるstack更新を検知するデーモン(cfn-hup)

が利用可能になります。他にもありますが、使ってないので触れません。

cfn-init

cfn-init ヘルパースクリプトは、AWS::CloudFormation::Init キーからテンプレートメタデータを読み取り、それに応じて様々な処理を実行します。(公式ドキュメント抜粋)
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-init.html

実行コマンド例

sudo /usr/local/bin/cfn-init -v --stack "YourStackName" --resource WebApp --configsets Install --region "YourRegion"

設定の読み込み

CloudFormationの中にResources.WebApp.Metadata.AWS::CloudFormation::Initという宣言を書き、その要素として実行コマンドを記述できます。
configSetsというパラメータに実行するtaskをまとめて指定することができるので、ここに必要なtaskを登録しましょう。

"Resources": {
    "WebApp": {
        "Metadata": {
            "AWS::CloudFormation::Init": {
                "configSets": {
                    "Install": [
                        "taskName1",
                        "taskName2",
                        "..."
                    ]
                },
                "taskName1": {
                    "..."
                },
                "taskName2": {
                    "..."
                },
                "..."
            }
        }
    }    
}

taskに指定できるコマンドにはいくつか種類があるので今回用いたものを書いていきます。

公式ドキュメントはこちら
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-init.html

packageインストール

aptコマンドを使ったパッケージインストールは下記のように記述すると必要なパッケージをインストールできます。

"taskName": {
    "packages": {
        "apt": {
            "nginx": [],
            "php7.2": [],
            "php-fpm": [],
            "php7.2-mysql": [],
            "php7.2-zip": [],
            "php7.2-mbstring": [],
            "php7.2-dom": [],
            "php7.2-curl": [],
            "composer": []
        }
    }
}

ファイル作成

例えばnginxの設定ファイルを作成したければ下記のような記述をすればパーミッションを指定した上で作成が可能です。

区切り文字を改行コードで指定しているため、複数行に渡って記述することができます。

"taksName": {
     "files": {
        "/etc/nginx/nginx.conf": {
            "content": {
                "Fn::Join": [
                    "\n",
                    [
                        "your settings",
                        "write here"
                    ]
                ]
            },
            "mode": "000400",
            "owner": "root",
            "group": "root"
        }
    }
}

コマンド実行

これは自由度が高いです。好きなコマンドを実行できます。

"taskName": {
    "commands": {
        "makeConfDir": {
            "command" : "sudo mkdir -p /etc/nginx/common"
        }
    }
}

プロセス起動

services キーを使用すると、インスタンスが起動されるときに有効化または無効化する必要のあるサービスを定義できます。(公式ドキュメント抜粋)

Linuxではsysvinitキーを指定することで初回起動時の振る舞いを指定できるようです。

用いたオプションの意味は下記のとおりです。
enabled: 起動時にサービスを自動的に開始させるかどうか
ensureRunning: cfn-init が終了した後でサービスを実行するかどうか
files: 読み込む設定ファイルのリスト

"taskName": {
     "services": {
         "sysvinit": {
             "cfn-hup": {
                 "enabled": "true",
                 "ensureRunning": "true",
                 "files": [
                     "/etc/cfn/cfn-hup.conf",
                     "/etc/cfn/hooks.d/cfn-auto-reloader.conf"
                 ]
             }
         }
     }
}

ログの場所

重要な情報が入ってるのでCloudFormationを使う時はこのログファイルを見ることになると思います。

# 詳細なログ
/var/log/cfn-init.log
# 結果だけ出力されるログ
/var/log/cfn-init-cmd.log

cfn-signal

cfn-initの実行後のステータスを引数に与えて使うことで状態を追跡できるようになります。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-signal.html

ドキュメントの通り、下記のように使って、cfn-init実行後のステータス$?を渡して実行します。


"UserData": {
    "Fn::Base64": {
        "Fn::Join": [
            "",
            [
                "#!/bin/bash -x\n",
                "# Install the files and packages from the metadata\n",
                "/opt/aws/bin/cfn-init -v ",
                "         --stack ",
                {
                    "Ref": "AWS::StackName"
                },
                "         --resource MyInstance ",
                "         --region ",
                {
                    "Ref": "AWS::Region"
                },
                "\n",
                "# Signal the status from cfn-init\n",
                "/opt/aws/bin/cfn-signal -e $? ",
                "         --stack ",
                {
                    "Ref": "AWS::StackName"
                },
                "         --resource MyInstance ",
                "         --region ",
                {
                    "Ref": "AWS::Region"
                },
                "\n"
            ]
        ]
    }
}

cfn-hup

cfn-hupは2つの設定ファイルを用いて動作させます。
/etc/cfn/cfn-hup.conf: 変更の検知
/etc/cfn/hooks.d/cfn-auto-reloader.conf: hook処理の定義

/etc/cfn/cfn-hup.confのintervalは変更があったかを調べる周期なので、開発中は短くしておくと便利です(デフォルト15分)。
hookの設定ファイルにupdateされたらcfn-initを実行するように書いておきます。

"files": {
    "/etc/cfn/cfn-hup.conf": {
        "content": {
            "Fn::Join": [
                "",
                [
                    "[main]\n",
                    "stack=",
                    {
                        "Ref": "AWS::StackId"
                    },
                    "\n",
                    "region=",
                    {
                        "Ref": "AWS::Region"
                    },
                    "\n",
                    "interval=5",
                    "\n"
                ]
            ]
        },
        "mode": "000400",
        "owner": "root",
        "group": "root"
    },
    "/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
        "content": {
            "Fn::Join": [
                "",
                [
                    "[cfn-auto-reloader-hook]\n",
                    "triggers=post.update\n",
                    "path=Resources.WebApp.Metadata.AWS::CloudFormation::Init\n",
                    "action=/usr/local/bin/cfn-init -v ",
                    "         --stack ",
                    {
                        "Ref": "AWS::StackName"
                    },
                    "         --resource WebApp ",
                    "         --configsets Install ",
                    "         --region ",
                    {
                        "Ref": "AWS::Region"
                    },
                    "\n",
                    "runas=root\n"
                ]
            ]
        },
        "mode": "000400",
        "owner": "root",
        "group": "root"
    }
}

設定ファイルのサンプル

Laravelはcomposerを使っており、設定ファイルはjson形式なのでCloudFormationの設定ファイルもjsonに合わせています。
本当はコメント使えないですが、便宜的に追加してます。

SecurityGroupやElastic IPについては特に困ったことは無かったので説明しませんでしたが、このサンプルに含まれています。

{
   "Resources": {
        # ssh接続するための設定
        "SSHGroup": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "GroupDescription": "Enable SSH access via port 22",
                "SecurityGroupIngress": [
                    {
                        "IpProtocol": "tcp",
                        "CidrIp": "0.0.0.0/0",
                        "FromPort": "22",
                        "ToPort": "22"
                    }
                ]
            }
        },
        # httpとhttps通信を許可する設定
        "httpGroup": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "GroupDescription": "Enable http access via port 80",
                "SecurityGroupIngress": [
                    {
                        "IpProtocol": "tcp",
                        "CidrIp": "0.0.0.0/0",
                        "FromPort": "80",
                        "ToPort": "80"
                    }
                ]
            }
        }, 
        # IPアドレスを固定するために割り当てる
        "WebServerEip": {
            "Type": "AWS::EC2::EIP",
            "Properties": {
                "InstanceId": {
                    "Ref": "WebApp"
                }
            }
        },
        # 立ち上げるEC2インスタンスの設定
        "WebApp": {
            "Type": "AWS::EC2::Instance",
            "Metadata": {
                "AWS::CloudFormation::Init": {
                    "configSets": {
                        "Install": [
                            "Install",
                            "SetupNginx",
                            "StartNginx"
                        ]
                    },
                    "Install": {
                        # 必要なパッケージのインストール
                        "packages": {
                            "apt": {
                                "nginx": [],
                                "php7.2": [],
                                "php-fpm": [],
                                "php7.2-mysql": [],
                                "php7.2-zip": [],
                                "php7.2-mbstring": [],
                                "php7.2-dom": [],
                                "php7.2-curl": [],
                                "composer": []
                            }
                        },
                        # 初期化時に実行するコマンド
                        "files": {
                            "/etc/cfn/cfn-hup.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "[main]\n",
                                            "stack=",
                                            {
                                                "Ref": "AWS::StackId"
                                            },
                                            "\n",
                                            "region=",
                                            {
                                                "Ref": "AWS::Region"
                                            },
                                            "\n",
                                            # 開発中はチェック頻度短くしたほうが楽(デフォルト15分)
                                            "interval=5",
                                            "\n"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            },
                            # stackを更新した時に反映させるためのデーモンの設定
                            "/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "[cfn-auto-reloader-hook]\n",
                                            "triggers=post.update\n",
                                            "path=Resources.WebApp.Metadata.AWS::CloudFormation::Init\n",
                                            "action=/usr/local/bin/cfn-init -v ",
                                            "         --stack ",
                                            {
                                                "Ref": "AWS::StackName"
                                            },
                                            "         --resource WebApp ",
                                            "         --configsets Install ",
                                            "         --region ",
                                            {
                                                "Ref": "AWS::Region"
                                            },
                                            "\n",
                                            "runas=root\n"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            }
                        },
                        # cfn-hupを初期起動させる
                        "services": {
                            "sysvinit": {
                                "cfn-hup": {
                                    "enabled": "true",
                                    "ensureRunning": "true",
                                    "files": [
                                        "/etc/cfn/cfn-hup.conf",
                                        "/etc/cfn/hooks.d/cfn-auto-reloader.conf"
                                    ]
                                }
                            }
                        }
                    },
                    # nginxの設定に必要なファイルやディレクトリを作成
                    "SetupNginx": {
                        # ここで作ったディレクトリはnginxの設定ファイルでincludeする必要がある
                        "commands": {
                            "makeConfDir": {
                                "command" : "sudo mkdir -p /etc/nginx/common"
                            }
                        },
                        "files": {
                            "/etc/nginx/nginx.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "\n",
                                        [
                                            "your settings"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            },
                            # サービス毎に分けて管理する
                            "/etc/nginx/sites-available/your_site.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "\n",
                                        [
                                            "your_settings"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            },
                            # php-fpmの設定ファイルを分けておく
                            "/etc/nginx/common/php_fastcgi.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "\n",
                                        [
                                            "your_settings"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            },
                            "/etc/nginx/common/general.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "\n",
                                        [
                                            "your settings"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            }
                        }
                    },
                    # 起動
                    "StartNginx": {
                        "commands" : {
                            "start": {
                                "command" : "sudo systemctl start nginx"
                            }
                        }
                    }
                }
            },
            "Properties": {
                # Ubuntu18.04のami
                "ImageId": "ami-0eb48a19a8d81e20b",
                "InstanceType": {
                    "Ref": "InstanceType"
                },
                "KeyName": {
                    "Ref": "KeyName"
                },
                "SecurityGroupIds": [
                    {
                        "Ref": "SSHGroup"
                    },
                    {
                        "Ref": "httpGroup"
                    }
                ],
          # 最初に実行するコマンドの指定
                "UserData": {
                    "Fn::Base64": {
                        "Fn::Join": [
                            "",
                            [
                                "#!/bin/bash -xe\n",
                                "sudo apt-get update\n",
                                "sudo apt-get -y install python-pip\n",
                                # cfn-hupが使えるようにする
                                "pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz\n",
                                "sudo cp -a /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup\n",
                                "chmod u+x /etc/init.d/cfn-hup\n",
                                "sudo update-rc.d cfn-hup defaults\n",
                                "sudo service cfn-hup start\n",
                                "# Install the files and packages from the metadata\n",
                                "sudo /usr/local/bin/cfn-init -v ",
                                "         --stack ",
                                {
                                    "Ref": "AWS::StackName"
                                },
                                "         --resource WebApp ",
                                "         --configsets Install ",
                                "         --region ",
                                {
                                    "Ref": "AWS::Region"
                                },
                                "\n",
                                # エラーハンドリング用
                                "# Signal the status from cfn-init\n",
                                "sudo /usr/local/bin/cfn-signal -e $? ",
                                "         --stack ",
                                {
                                    "Ref": "AWS::StackName"
                                },
                                "         --resource WebApp ",
                                "         --region ",
                                {
                                    "Ref": "AWS::Region"
                                },
                                "\n"
                            ]
                        ]
                    }
                }
            }
        }
    },
    # stack作成時にオプション選択するパラメータを指定
    "Parameters": {
        "KeyName": {
            "Description": "Name of an existing EC2 KeyPair to enable SSH access to the web server",
            "Type": "String",
            "Default": "your_key"
        },
        "InstanceType": {
            "Description": "WebServer EC2 instance type",
            "Type": "String",
            "Default": "t2.micro",
            "AllowedValues": [
                "t1.micro",
                "t2.nano",
                "t2.micro"
            ],
            "ConstraintDescription": "must be a valid EC2 instance type."
        }
    },
    # stackの状態を見た時に表示される内容
    "Outputs": {
        "WebServerIpAddress": {
            "Value": {
                "Fn::GetAtt": [
                    "WebApp",
                    "PublicIp"
                ]
            },
            "Description": "IP Address of WebServer"
        },
        "WebServerAvailabilityZone": {
            "Value": {
                "Fn::GetAtt": [
                    "WebApp",
                    "AvailabilityZone"
                ]
            },
            "Description": "AvailabilityZone of WebServer"
        },
        "SSHToWebServer": {
            "Value": {
                "Fn::Join": [
                    "",
                    [
                        "ssh -i /path/to/",
                        {
                            "Ref": "KeyName"
                        },
                        ".pem",
                        " ec2-user@",
                        {
                            "Fn::GetAtt": [
                                "WebApp",
                                "PublicIp"
                            ]
                        }
                    ]
                ]
            },
            "Description": "SSH command to connect WebServer"
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CloudFormationでLaravel5.5が動く環境をEC2(Ubutu18.04)上に作ったときのまとめ

GWだったので趣味で開発してるLaravelプロジェクトをEC2で動かすために必要な環境をCloudFormationで立ててみました。その時に必要となった知識をまとめておきます。

必要な環境

  • OS

    • Ubuntu18.04
  • 必要サーバ

    • EC2インスタンス1台
  • インストールが必要なもの(ざっくり)

    • php7.2
    • php-fpm
    • composer
    • Laravelの動作に必要なPHPのライブラリ群
    • nginx
  • 追加で必要なサービス

    • Elastic IP
  • Security Groupの要件

    • ssh接続の許可
    • http接続の許可

CloudFormationによるインスタンスの管理

CloudFormationには初期化コマンドを実行するための仕組みをpythonスクリプトで提供していますが、Ubuntu18.04にはデフォルトで備えられていないので自前インストールします。pipを使って下記のコマンドで実現できました。

sudo apt-get update
sudo apt-get -y install python-pip
pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz
sudo cp -a /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup
chmod u+x /etc/init.d/cfn-hup
sudo update-rc.d cfn-hup defaults
sudo service cfn-hup start

これにより

  • テンプレート内でパッケージインストールなどをパラメータで指定して実行(cfn-init)
  • cfn-initで実行したコマンドのステータスを見て通達(cfn-signal)
  • テンプレート更新によるstack更新を検知するデーモン(cfn-hup)

が利用可能になります。他にもありますが、使ってないので触れません。

cfn-init

cfn-init ヘルパースクリプトは、AWS::CloudFormation::Init キーからテンプレートメタデータを読み取り、それに応じて様々な処理を実行します。(公式ドキュメント抜粋)
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-init.html

実行コマンド例

sudo /usr/local/bin/cfn-init -v --stack "YourStackName" --resource WebApp --configsets Install --region "YourRegion"

設定の読み込み

CloudFormationの中にResources.WebApp.Metadata.AWS::CloudFormation::Initという宣言を書き、その要素として実行コマンドを記述できます。
configSetsというパラメータに実行するtaskをまとめて指定することができるので、ここに必要なtaskを登録しましょう。

"Resources": {
    "WebApp": {
        "Metadata": {
            "AWS::CloudFormation::Init": {
                "configSets": {
                    "Install": [
                        "taskName1",
                        "taskName2",
                        "..."
                    ]
                },
                "taskName1": {
                    "..."
                },
                "taskName2": {
                    "..."
                },
                "..."
            }
        }
    }    
}

taskに指定できるコマンドにはいくつか種類があるので今回用いたものを書いていきます。

公式ドキュメントはこちら
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-init.html

packageインストール

aptコマンドを使ったパッケージインストールは下記のように記述すると必要なパッケージをインストールできます。

"taskName": {
    "packages": {
        "apt": {
            "nginx": [],
            "php7.2": [],
            "php-fpm": [],
            "php7.2-mysql": [],
            "php7.2-zip": [],
            "php7.2-mbstring": [],
            "php7.2-dom": [],
            "php7.2-curl": [],
            "composer": []
        }
    }
}

ファイル作成

例えばnginxの設定ファイルを作成したければ下記のような記述をすればパーミッションを指定した上で作成が可能です。

区切り文字を改行コードで指定しているため、複数行に渡って記述することができます。

"taksName": {
     "files": {
        "/etc/nginx/nginx.conf": {
            "content": {
                "Fn::Join": [
                    "\n",
                    [
                        "your settings",
                        "write here"
                    ]
                ]
            },
            "mode": "000400",
            "owner": "root",
            "group": "root"
        }
    }
}

コマンド実行

これは自由度が高いです。好きなコマンドを実行できます。

"taskName": {
    "commands": {
        "makeConfDir": {
            "command" : "sudo mkdir -p /etc/nginx/common"
        }
    }
}

プロセス起動

services キーを使用すると、インスタンスが起動されるときに有効化または無効化する必要のあるサービスを定義できます。(公式ドキュメント抜粋)

Linuxではsysvinitキーを指定することで初回起動時の振る舞いを指定できるようです。

用いたオプションの意味は下記のとおりです。
enabled: 起動時にサービスを自動的に開始させるかどうか
ensureRunning: cfn-init が終了した後でサービスを実行するかどうか
files: 読み込む設定ファイルのリスト

"taskName": {
     "services": {
         "sysvinit": {
             "cfn-hup": {
                 "enabled": "true",
                 "ensureRunning": "true",
                 "files": [
                     "/etc/cfn/cfn-hup.conf",
                     "/etc/cfn/hooks.d/cfn-auto-reloader.conf"
                 ]
             }
         }
     }
}

ログの場所

重要な情報が入ってるのでCloudFormationを使う時はこのログファイルを見ることになると思います。

# 詳細なログ
/var/log/cfn-init.log
# 結果だけ出力されるログ
/var/log/cfn-init-cmd.log

cfn-signal

cfn-initの実行後のステータスを引数に与えて使うことで状態を追跡できるようになります。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-signal.html

ドキュメントの通り、下記のように使って、cfn-init実行後のステータス$?を渡して実行します。


"UserData": {
    "Fn::Base64": {
        "Fn::Join": [
            "",
            [
                "#!/bin/bash -x\n",
                "# Install the files and packages from the metadata\n",
                "/opt/aws/bin/cfn-init -v ",
                "         --stack ",
                {
                    "Ref": "AWS::StackName"
                },
                "         --resource MyInstance ",
                "         --region ",
                {
                    "Ref": "AWS::Region"
                },
                "\n",
                "# Signal the status from cfn-init\n",
                "/opt/aws/bin/cfn-signal -e $? ",
                "         --stack ",
                {
                    "Ref": "AWS::StackName"
                },
                "         --resource MyInstance ",
                "         --region ",
                {
                    "Ref": "AWS::Region"
                },
                "\n"
            ]
        ]
    }
}

cfn-hup

cfn-hupは2つの設定ファイルを用いて動作させます。
/etc/cfn/cfn-hup.conf: 変更の検知
/etc/cfn/hooks.d/cfn-auto-reloader.conf: hook処理の定義

/etc/cfn/cfn-hup.confのintervalは変更があったかを調べる周期なので、開発中は短くしておくと便利です(デフォルト15分)。
hookの設定ファイルにupdateされたらcfn-initを実行するように書いておきます。

"files": {
    "/etc/cfn/cfn-hup.conf": {
        "content": {
            "Fn::Join": [
                "",
                [
                    "[main]\n",
                    "stack=",
                    {
                        "Ref": "AWS::StackId"
                    },
                    "\n",
                    "region=",
                    {
                        "Ref": "AWS::Region"
                    },
                    "\n",
                    "interval=5",
                    "\n"
                ]
            ]
        },
        "mode": "000400",
        "owner": "root",
        "group": "root"
    },
    "/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
        "content": {
            "Fn::Join": [
                "",
                [
                    "[cfn-auto-reloader-hook]\n",
                    "triggers=post.update\n",
                    "path=Resources.WebApp.Metadata.AWS::CloudFormation::Init\n",
                    "action=/usr/local/bin/cfn-init -v ",
                    "         --stack ",
                    {
                        "Ref": "AWS::StackName"
                    },
                    "         --resource WebApp ",
                    "         --configsets Install ",
                    "         --region ",
                    {
                        "Ref": "AWS::Region"
                    },
                    "\n",
                    "runas=root\n"
                ]
            ]
        },
        "mode": "000400",
        "owner": "root",
        "group": "root"
    }
}

設定ファイルのサンプル

Laravelはcomposerを使っており、設定ファイルはjson形式なのでCloudFormationの設定ファイルもjsonに合わせています。
本当はコメント使えないですが、便宜的に追加してます。

SecurityGroupやElastic IPについては特に困ったことは無かったので説明しませんでしたが、このサンプルに含まれています。

{
   "Resources": {
        # ssh接続するための設定
        "SSHGroup": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "GroupDescription": "Enable SSH access via port 22",
                "SecurityGroupIngress": [
                    {
                        "IpProtocol": "tcp",
                        "CidrIp": "0.0.0.0/0",
                        "FromPort": "22",
                        "ToPort": "22"
                    }
                ]
            }
        },
        # httpとhttps通信を許可する設定
        "httpGroup": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "GroupDescription": "Enable http access via port 80",
                "SecurityGroupIngress": [
                    {
                        "IpProtocol": "tcp",
                        "CidrIp": "0.0.0.0/0",
                        "FromPort": "80",
                        "ToPort": "80"
                    }
                ]
            }
        }, 
        # IPアドレスを固定するために割り当てる
        "WebServerEip": {
            "Type": "AWS::EC2::EIP",
            "Properties": {
                "InstanceId": {
                    "Ref": "WebApp"
                }
            }
        },
        # 立ち上げるEC2インスタンスの設定
        "WebApp": {
            "Type": "AWS::EC2::Instance",
            "Metadata": {
                "AWS::CloudFormation::Init": {
                    "configSets": {
                        "Install": [
                            "Install",
                            "SetupNginx",
                            "StartNginx"
                        ]
                    },
                    "Install": {
                        # 必要なパッケージのインストール
                        "packages": {
                            "apt": {
                                "nginx": [],
                                "php7.2": [],
                                "php-fpm": [],
                                "php7.2-mysql": [],
                                "php7.2-zip": [],
                                "php7.2-mbstring": [],
                                "php7.2-dom": [],
                                "php7.2-curl": [],
                                "composer": []
                            }
                        },
                        # 初期化時に実行するコマンド
                        "files": {
                            "/etc/cfn/cfn-hup.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "[main]\n",
                                            "stack=",
                                            {
                                                "Ref": "AWS::StackId"
                                            },
                                            "\n",
                                            "region=",
                                            {
                                                "Ref": "AWS::Region"
                                            },
                                            "\n",
                                            # 開発中はチェック頻度短くしたほうが楽(デフォルト15分)
                                            "interval=5",
                                            "\n"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            },
                            # stackを更新した時に反映させるためのデーモンの設定
                            "/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "",
                                        [
                                            "[cfn-auto-reloader-hook]\n",
                                            "triggers=post.update\n",
                                            "path=Resources.WebApp.Metadata.AWS::CloudFormation::Init\n",
                                            "action=/usr/local/bin/cfn-init -v ",
                                            "         --stack ",
                                            {
                                                "Ref": "AWS::StackName"
                                            },
                                            "         --resource WebApp ",
                                            "         --configsets Install ",
                                            "         --region ",
                                            {
                                                "Ref": "AWS::Region"
                                            },
                                            "\n",
                                            "runas=root\n"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            }
                        },
                        # cfn-hupを初期起動させる
                        "services": {
                            "sysvinit": {
                                "cfn-hup": {
                                    "enabled": "true",
                                    "ensureRunning": "true",
                                    "files": [
                                        "/etc/cfn/cfn-hup.conf",
                                        "/etc/cfn/hooks.d/cfn-auto-reloader.conf"
                                    ]
                                }
                            }
                        }
                    },
                    # nginxの設定に必要なファイルやディレクトリを作成
                    "SetupNginx": {
                        # ここで作ったディレクトリはnginxの設定ファイルでincludeする必要がある
                        "commands": {
                            "makeConfDir": {
                                "command" : "sudo mkdir -p /etc/nginx/common"
                            }
                        },
                        "files": {
                            "/etc/nginx/nginx.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "\n",
                                        [
                                            "your settings"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            },
                            # サービス毎に分けて管理する
                            "/etc/nginx/sites-available/your_site.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "\n",
                                        [
                                            "your_settings"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            },
                            # php-fpmの設定ファイルを分けておく
                            "/etc/nginx/common/php_fastcgi.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "\n",
                                        [
                                            "your_settings"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            },
                            "/etc/nginx/common/general.conf": {
                                "content": {
                                    "Fn::Join": [
                                        "\n",
                                        [
                                            "your settings"
                                        ]
                                    ]
                                },
                                "mode": "000400",
                                "owner": "root",
                                "group": "root"
                            }
                        }
                    },
                    # 起動
                    "StartNginx": {
                        "commands" : {
                            "start": {
                                "command" : "sudo systemctl start nginx"
                            }
                        }
                    }
                }
            },
            "Properties": {
                # Ubuntu18.04のami
                "ImageId": "ami-0eb48a19a8d81e20b",
                "InstanceType": {
                    "Ref": "InstanceType"
                },
                "KeyName": {
                    "Ref": "KeyName"
                },
                "SecurityGroupIds": [
                    {
                        "Ref": "SSHGroup"
                    },
                    {
                        "Ref": "httpGroup"
                    }
                ],
          # 最初に実行するコマンドの指定
                "UserData": {
                    "Fn::Base64": {
                        "Fn::Join": [
                            "",
                            [
                                "#!/bin/bash -xe\n",
                                "sudo apt-get update\n",
                                "sudo apt-get -y install python-pip\n",
                                # cfn-hupが使えるようにする
                                "pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz\n",
                                "sudo cp -a /usr/local/init/ubuntu/cfn-hup /etc/init.d/cfn-hup\n",
                                "chmod u+x /etc/init.d/cfn-hup\n",
                                "sudo update-rc.d cfn-hup defaults\n",
                                "sudo service cfn-hup start\n",
                                "# Install the files and packages from the metadata\n",
                                "sudo /usr/local/bin/cfn-init -v ",
                                "         --stack ",
                                {
                                    "Ref": "AWS::StackName"
                                },
                                "         --resource WebApp ",
                                "         --configsets Install ",
                                "         --region ",
                                {
                                    "Ref": "AWS::Region"
                                },
                                "\n",
                                # エラーハンドリング用
                                "# Signal the status from cfn-init\n",
                                "sudo /usr/local/bin/cfn-signal -e $? ",
                                "         --stack ",
                                {
                                    "Ref": "AWS::StackName"
                                },
                                "         --resource WebApp ",
                                "         --region ",
                                {
                                    "Ref": "AWS::Region"
                                },
                                "\n"
                            ]
                        ]
                    }
                }
            }
        }
    },
    # stack作成時にオプション選択するパラメータを指定
    "Parameters": {
        "KeyName": {
            "Description": "Name of an existing EC2 KeyPair to enable SSH access to the web server",
            "Type": "String",
            "Default": "your_key"
        },
        "InstanceType": {
            "Description": "WebServer EC2 instance type",
            "Type": "String",
            "Default": "t2.micro",
            "AllowedValues": [
                "t1.micro",
                "t2.nano",
                "t2.micro"
            ],
            "ConstraintDescription": "must be a valid EC2 instance type."
        }
    },
    # stackの状態を見た時に表示される内容
    "Outputs": {
        "WebServerIpAddress": {
            "Value": {
                "Fn::GetAtt": [
                    "WebApp",
                    "PublicIp"
                ]
            },
            "Description": "IP Address of WebServer"
        },
        "WebServerAvailabilityZone": {
            "Value": {
                "Fn::GetAtt": [
                    "WebApp",
                    "AvailabilityZone"
                ]
            },
            "Description": "AvailabilityZone of WebServer"
        },
        "SSHToWebServer": {
            "Value": {
                "Fn::Join": [
                    "",
                    [
                        "ssh -i /path/to/",
                        {
                            "Ref": "KeyName"
                        },
                        ".pem",
                        " ec2-user@",
                        {
                            "Fn::GetAtt": [
                                "WebApp",
                                "PublicIp"
                            ]
                        }
                    ]
                ]
            },
            "Description": "SSH command to connect WebServer"
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP Warning: require(/path/to/vendor/composer/../symfony/polyfill-mbstring/bootstrap.php): failed to open stream: No such file or directory in /path/to/vendor/composer/autoload_real.php on line 66となったときの対応方法

  • 環境
    • macOS Mojave バージョン10.14.4
    • PHP 7.3.1 (cli)
    • Composer version 1.8.0

事象 : Laravelのバージョンを確認しようとしたら怒られた。

$ php artisan -V
PHP Warning:  require(/path/to/vendor/composer/../symfony/polyfill-mbstring/bootstrap.php): failed to open stream: No such file or directory in /path/to/vendor/composer/autoload_real.php on line 66

Warning: require(/path/to/vendor/composer/../symfony/polyfill-mbstring/bootstrap.php): failed to open stream: No such file or directory in /path/to/vendor/composer/autoload_real.php on line 66
PHP Fatal error:  require(): Failed opening required '/path/to/vendor/composer/../symfony/polyfill-mbstring/bootstrap.php' (include_path='.:/usr/local/Cellar/php/7.3.1/share/php/pear') in /path/to/vendor/composer/autoload_real.php on line 66

Fatal error: require(): Failed opening required '/path/to/vendor/composer/../symfony/polyfill-mbstring/bootstrap.php' (include_path='.:/usr/local/Cellar/php/7.3.1/share/php/pear') in /path/to/vendor/composer/autoload_real.php on line 66

原因 : 必要なファイルがvenderディレクトリにないから

symfony - laravel on cpanel raise error for autoload_real.php on line 66 - Stack Overflow

対応 : composer updateする

$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 77 installs, 4 updates, 0 removals
  - Installing sebastian/resource-operations (2.0.1): Loading from cache
  - Installing sebastian/object-reflector (1.1.1): Loading from cache
  - Installing sebastian/recursion-context (3.0.0): Loading from cache
  - Installing sebastian/object-enumerator (3.0.3): Loading from cache
  - Installing sebastian/exporter (3.1.0): Loading from cache
  - Installing sebastian/environment (4.2.2): Downloading (100%)         
  - Installing sebastian/diff (3.0.2): Loading from cache
  - Installing sebastian/comparator (3.0.2): Loading from cache
  - Installing phpunit/php-timer (2.1.1): Loading from cache
  - Installing phpunit/php-file-iterator (2.0.2): Loading from cache
  - Updating theseer/tokenizer (1.1.0 => 1.1.2): Loading from cache
  - Installing sebastian/code-unit-reverse-lookup (1.0.1): Loading from cache
  - Installing phpunit/php-token-stream (3.0.1): Loading from cache
  - Installing phpunit/php-code-coverage (6.1.4): Loading from cache
  - Installing doctrine/instantiator (1.2.0): Loading from cache
  - Updating symfony/polyfill-ctype (v1.10.0 => v1.11.0): Loading from cache
  - Updating phpdocumentor/reflection-docblock (4.3.0 => 4.3.1): Downloading (100%)         
  - Installing phpspec/prophecy (1.8.0): Loading from cache
  - Installing phar-io/manifest (1.0.3): Loading from cache
  - Installing myclabs/deep-copy (1.9.1): Loading from cache
  - Updating phpunit/phpunit (7.5.6 => 7.5.9): Loading from cache
  - Installing vlucas/phpdotenv (v2.6.1): Loading from cache
  - Installing symfony/css-selector (v4.2.8): Loading from cache
  - Installing tijsverkoyen/css-to-inline-styles (2.2.1): Loading from cache
  - Installing symfony/polyfill-php72 (v1.11.0): Loading from cache
  - Installing symfony/polyfill-mbstring (v1.11.0): Loading from cache
  - Installing symfony/var-dumper (v4.2.8): Downloading (100%)         
  - Installing symfony/routing (v4.2.8): Downloading (100%)         
  - Installing symfony/process (v4.2.8): Loading from cache
  - Installing psr/log (1.1.0): Loading from cache
  - Installing symfony/debug (v4.2.8): Loading from cache
  - Installing symfony/http-foundation (v4.2.8): Downloading (100%)         
  - Installing symfony/polyfill-intl-idn (v1.11.0): Loading from cache
  - Installing symfony/contracts (v1.0.2): Loading from cache
  - Installing symfony/event-dispatcher (v4.2.8): Loading from cache
  - Installing symfony/http-kernel (v4.2.8): Downloading (100%)         
  - Installing symfony/finder (v4.2.8): Loading from cache
  - Installing symfony/console (v4.2.8): Loading from cache
  - Installing symfony/polyfill-iconv (v1.11.0): Loading from cache
  - Installing doctrine/lexer (v1.0.1): Loading from cache
  - Installing egulias/email-validator (2.1.7): Loading from cache
  - Installing swiftmailer/swiftmailer (v6.2.1): Loading from cache
  - Installing paragonie/random_compat (v9.99.99): Loading from cache
  - Installing ramsey/uuid (3.8.0): Loading from cache
  - Installing psr/simple-cache (1.0.1): Loading from cache
  - Installing psr/container (1.0.0): Loading from cache
  - Installing opis/closure (3.2.0): Downloading (100%)         
  - Installing symfony/translation (v4.2.8): Downloading (100%)         
  - Installing nesbot/carbon (1.37.1): Downloading (100%)         
  - Installing monolog/monolog (1.24.0): Loading from cache
  - Installing league/flysystem (1.0.51): Loading from cache
  - Installing erusev/parsedown (1.7.3): Loading from cache
  - Installing dragonmantank/cron-expression (v2.3.0): Loading from cache
  - Installing nikic/php-parser (v4.2.1): Loading from cache
  - Installing doctrine/inflector (v1.3.0): Loading from cache
  - Installing ralouphie/getallheaders (2.0.5): Loading from cache
  - Installing psr/http-message (1.0.1): Loading from cache
  - Installing guzzlehttp/psr7 (1.5.2): Loading from cache
  - Installing guzzlehttp/promises (v1.3.1): Loading from cache
  - Installing guzzlehttp/guzzle (6.3.3): Loading from cache
  - Installing laravel/slack-notification-channel (v1.0.3): Loading from cache
  - Installing laravel/framework (v5.7.28): Downloading (100%)         
  - Installing lcobucci/jwt (3.2.5): Loading from cache
  - Installing php-http/promise (v1.0.0): Loading from cache
  - Installing php-http/httplug (v1.1.0): Loading from cache
  - Installing php-http/guzzle6-adapter (v1.1.1): Loading from cache
  - Installing zendframework/zend-diactoros (1.8.6): Loading from cache
  - Installing nexmo/client (1.7.0): Downloading (100%)         
  - Installing laravel/nexmo-notification-channel (v1.0.1): Loading from cache
  - Installing fideloper/proxy (4.1.0): Loading from cache
  - Installing jakub-onderka/php-console-color (v0.2): Loading from cache
  - Installing jakub-onderka/php-console-highlighter (v0.4): Loading from cache
  - Installing dnoegel/php-xdg-base-dir (0.1): Loading from cache
  - Installing psy/psysh (v0.9.9): Loading from cache
  - Installing laravel/tinker (v1.0.8): Loading from cache
  - Installing beyondcode/laravel-dump-server (1.2.2): Loading from cache
  - Installing fzaninotto/faker (v1.8.0): Loading from cache
  - Installing hamcrest/hamcrest-php (v2.0.0): Loading from cache
  - Installing mockery/mockery (1.2.2): Loading from cache
  - Installing filp/whoops (2.3.1): Loading from cache
  - Installing nunomaduro/collision (v2.1.1): Loading from cache
phpunit/php-code-coverage suggests installing ext-xdebug (^2.6.0)
symfony/routing suggests installing symfony/config (For using the all-in-one router or any loader)
symfony/routing suggests installing symfony/yaml (For using the YAML loader)
symfony/routing suggests installing symfony/expression-language (For using expression matching)
symfony/routing suggests installing doctrine/annotations (For using the annotation loader)
symfony/contracts suggests installing psr/cache (When using the Cache contracts)
symfony/contracts suggests installing symfony/cache-contracts-implementation
symfony/contracts suggests installing symfony/service-contracts-implementation
symfony/event-dispatcher suggests installing symfony/dependency-injection
symfony/http-kernel suggests installing symfony/browser-kit
symfony/http-kernel suggests installing symfony/config
symfony/http-kernel suggests installing symfony/dependency-injection
symfony/console suggests installing symfony/lock
swiftmailer/swiftmailer suggests installing true/punycode (Needed to support internationalized email addresses, if ext-intl is not installed)
paragonie/random_compat suggests installing ext-libsodium (Provides a modern crypto API that can be used to generate random bytes.)
ramsey/uuid suggests installing ircmaxell/random-lib (Provides RandomLib for use with the RandomLibAdapter)
ramsey/uuid suggests installing ext-libsodium (Provides the PECL libsodium extension for use with the SodiumRandomGenerator)
ramsey/uuid suggests installing ext-uuid (Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator)
ramsey/uuid suggests installing moontoast/math (Provides support for converting UUID to 128-bit integer (in string form).)
ramsey/uuid suggests installing ramsey/uuid-doctrine (Allows the use of Ramsey\Uuid\Uuid as Doctrine field type.)
ramsey/uuid suggests installing ramsey/uuid-console (A console application for generating UUIDs with ramsey/uuid)
symfony/translation suggests installing symfony/config
symfony/translation suggests installing symfony/yaml
nesbot/carbon suggests installing friendsofphp/php-cs-fixer (Needed for the `composer phpcs` command. Allow to automatically fix code style.)
nesbot/carbon suggests installing phpstan/phpstan (Needed for the `composer phpstan` command. Allow to detect potential errors.)
monolog/monolog suggests installing graylog2/gelf-php (Allow sending log messages to a GrayLog2 server)
monolog/monolog suggests installing sentry/sentry (Allow sending log messages to a Sentry server)
monolog/monolog suggests installing doctrine/couchdb (Allow sending log messages to a CouchDB server)
monolog/monolog suggests installing ruflin/elastica (Allow sending log messages to an Elastic Search server)
monolog/monolog suggests installing php-amqplib/php-amqplib (Allow sending log messages to an AMQP server using php-amqplib)
monolog/monolog suggests installing ext-amqp (Allow sending log messages to an AMQP server (1.0+ required))
monolog/monolog suggests installing ext-mongo (Allow sending log messages to a MongoDB server)
monolog/monolog suggests installing mongodb/mongodb (Allow sending log messages to a MongoDB server via PHP Driver)
monolog/monolog suggests installing aws/aws-sdk-php (Allow sending log messages to AWS services like DynamoDB)
monolog/monolog suggests installing rollbar/rollbar (Allow sending log messages to Rollbar)
monolog/monolog suggests installing php-console/php-console (Allow sending log messages to Google Chrome)
league/flysystem suggests installing league/flysystem-eventable-filesystem (Allows you to use EventableFilesystem)
league/flysystem suggests installing league/flysystem-rackspace (Allows you to use Rackspace Cloud Files)
league/flysystem suggests installing league/flysystem-azure (Allows you to use Windows Azure Blob storage)
league/flysystem suggests installing league/flysystem-webdav (Allows you to use WebDAV storage)
league/flysystem suggests installing league/flysystem-aws-s3-v2 (Allows you to use S3 storage with AWS SDK v2)
league/flysystem suggests installing league/flysystem-aws-s3-v3 (Allows you to use S3 storage with AWS SDK v3)
league/flysystem suggests installing spatie/flysystem-dropbox (Allows you to use Dropbox storage)
league/flysystem suggests installing srmklive/flysystem-dropbox-v2 (Allows you to use Dropbox storage for PHP 5 applications)
league/flysystem suggests installing league/flysystem-cached-adapter (Flysystem adapter decorator for metadata caching)
league/flysystem suggests installing league/flysystem-sftp (Allows you to use SFTP server storage via phpseclib)
league/flysystem suggests installing league/flysystem-ziparchive (Allows you to use ZipArchive adapter)
laravel/framework suggests installing aws/aws-sdk-php (Required to use the SQS queue driver and SES mail driver (^3.0).)
laravel/framework suggests installing doctrine/dbal (Required to rename columns and drop SQLite columns (^2.6).)
laravel/framework suggests installing league/flysystem-aws-s3-v3 (Required to use the Flysystem S3 driver (^1.0).)
laravel/framework suggests installing league/flysystem-cached-adapter (Required to use the Flysystem cache (^1.0).)
laravel/framework suggests installing league/flysystem-rackspace (Required to use the Flysystem Rackspace driver (^1.0).)
laravel/framework suggests installing league/flysystem-sftp (Required to use the Flysystem SFTP driver (^1.0).)
laravel/framework suggests installing moontoast/math (Required to use ordered UUIDs (^1.1).)
laravel/framework suggests installing pda/pheanstalk (Required to use the beanstalk queue driver (^3.0|^4.0).)
laravel/framework suggests installing predis/predis (Required to use the redis cache and queue drivers (^1.0).)
laravel/framework suggests installing pusher/pusher-php-server (Required to use the Pusher broadcast driver (^3.0).)
laravel/framework suggests installing symfony/dom-crawler (Required to use most of the crawler integration testing tools (^4.1).)
laravel/framework suggests installing symfony/psr-http-message-bridge (Required to psr7 bridging features (^1.0).)
lcobucci/jwt suggests installing mdanter/ecc (Required to use Elliptic Curves based algorithms.)
psy/psysh suggests installing ext-pdo-sqlite (The doc command requires SQLite to work.)
psy/psysh suggests installing hoa/console (A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit.)
filp/whoops suggests installing whoops/soap (Formats errors as SOAP responses)
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: beyondcode/laravel-dump-server
Discovered Package: fideloper/proxy
Discovered Package: laravel/nexmo-notification-channel
Discovered Package: laravel/slack-notification-channel
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.

# 治りました。
$ php artisan -V
Laravel Framework 5.7.28

この間は問題なかったのに・・・なぜ?

ちょっと前の結果
$ php artisan -V
Laravel Framework 5.8.14

何かが更新でもされたのだろうか?それともなにかやったのを忘却しているのだろうか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php-master-changes 2019-05-05

今日は typo の修正があった!

2019-05-05

cmb69: Fix typo

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

https対応したLaravelプロジェクトでassetを使う時の注意点

Laravel5.5製のWebアプリケーションをhttps化させた時に遭遇した問題とその解決方法について書きます。

読み込みファイルのパスのプロトコルが変わらない問題

Laravelにはviewでcssやjsを読み込む際にフルパスを記述するためのメソッドが用意されています。

たとえばcssを読み込みたい時は

<link href="{{ asset('css/your_app.css') }}" rel="stylesheet">

jsを読み込みたい時は

<script src="{{ asset('js/your_app.js') }}"></script>

と書くことができます。
これらは実際にローカルでページにアクセスすると

# css
http://localhost/css/your_app.css
# js
http://localhost/js/your_app.js

のように展開されます。
headerの情報を元に参照hostを判断して動的に生成してくれます。

しかし、なぜかプロトコルのところはアクセス元の情報に沿わずにhttpとして展開されてしまいます。

https://your_domain にアクセスしたら

# css
http://your_domain/css/your_app.css
# js
http://your_domain/js/your_app.js

と展開されます。
これだとcssとjsのロードエラーがフロントで発生するため、何かしら回避策が必要です。

回避方法

そこで、https通信のときだけassetで返されるURLをhttpsにすることにしました。

まずhttpsかどうかの判定にはリクエストオブジェクトから呼び出せるisSecureというメソッドを使って判定しました。
これは単純にアクセス元のプロトコルだけでなく、headerのX_FORWARDED_PROTOも見てくれるので、AWSでロードバランサーを介してhttps化している場合でも対応が可能です。

ただし、ロードバランサー経由の場合は追加で

TrustProxies.php
protected $proxies = '**';

と記述する必要があります。
Laravelはデフォルトではプロキシー経由のリクエストにおけるhttpsを許容していないらしく、TrustProxies.phpに追記する必要があります。AWSのロードバランサーはipアドレスが動的に変わるためワイルドカードを指定します。
参考: https://readouble.com/laravel/5.5/ja/requests.html#configuring-trusted-proxies

そして、URL生成でhttpsを強制するための方法としてforceSchemeというメソッドが用意されているので、これを使いました。
https://laravel.com/api/5.5/Illuminate/Routing/UrlGenerator.html#method_forceScheme

AppServiceProviderのboot()の中に下記の記述をすることで対応できます。

AppServiceProvide.php
    public function boot()
    {
        if (request()->isSecure()) {
            \URL::forceScheme('https');
        }
    }

これでhttpでもhttpsでも大丈夫になりました。

最後に

調べていると本番環境は常にhttpsにするって方法がちらほら出てきましたが、ステージング環境とか必要箇所が増えるたびに環境変数の対応箇所が増えてきてしまうので、単純に通信のプロトコルをみて判断できる実装のほうが良いのではないかなと思いました。

また、こういうエラーはローカルでは検証しづらく、httpsな環境にあげたときにしか気づけ無いので、httpsなステージング環境は必要ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】【Laravel】League of Legendsの公式からAPIキーを取得する。

きっかけ

プログラミングを学ぶことも楽しいが、ゲームもやはり楽しい。(特にlol)
最近プラチナに昇格して、ほくほくである。
プログラミングもようやく色々とできるようになったので、いっちょここらで自作のwebサイトを作ろうかと意気込んでいた。

そうだlolのデータを取得して分析サイト(個人趣味)を作ろう!

思い立ったが吉日。早速設計することにした。(頭の中で)
lolというゲームにおいて、忘れてはならないのが、OP.GGというサイトである。
このサイトで自分のゲームの中の名前を入れるやいなやものすごい量の戦績データなどを振り返ることのできる
神サイトである。
また、チャンピオンの勝率や、そのチャンピオンの使用武器ごとの勝率などとにかくすごい。
僕自身かなりお世話になったサイトである。僕をあおってきたやつの名前を検索してランクをあざ笑ったりした

なので、最初はそのサイトをスクレイピングして~(いいのかわからんが)と考えていたのだが、検索したところ。。。

Riotから既にAPIが公開されているではないかあああああ!!!!

まさにS8途中にADCがADCでないメタが流行ったばりに衝撃を受けた。
lolネタがわかるひとならきっと伝わっていると思う。

参考

Riot Games APIでLeague of Legendsの試合データを取得しよう
RiotAPIを使ってLP変動を調べてみる

公式サイト

幸運にも偉大なる先人たちがやってくださっていた。ありがとうございます。ちょっと記事が古いけども

実践

今回の流れとしては、公式から発行されているAPIキーを使って自身に紐づくサモナーIDを取得して画面に表示しちゃおうというもの
まずはrouteから

route

image.png

シンプルなルーティング。indexControllerのgetIDapiメソッドを使う。

controller

image.png

変数api_keyには、公式から発行されるAPIキーを入力する。
変数summoner_nameには、自分のサモナーネームを入れる。
変数regionには、自分のやっているサーバーを入れる。日本ならjp、NAならna

上記の変数は別に作成せずとも変数urlの中にべた書きで問題ない。変数urlさえ正しく作れていればOK

その後は、curlを使ってサーバーへリクエストを送り、サモナーIDを手に入れるという形。
この辺は参考サイト2番目のほうで詳しくやっていただいている。

セッションの項目は僕が勝手に作ってるだけなので気にしないでください。

取得し終えたら、api.blade.phpへ変数resultを返す。
この変数resultは配列になっているので注意。

view

image.png

自分好みでわんわんおやらふざけているが最悪、11行目がかけていれば大丈夫。
ボタンの部分は今後自分が作る予定のために書いているので気にしないでください。
ソースがふざけすぎててわらった

実行結果

image.png

なんかこんな感じでidやらサモナーレベルやら出たら合格!
nameには自分のサモナーネームが入っているよ。
(一応怖いので一部マスクしました。)

注意点

ハマってしまった箇所が一点だけ。。。
参考サイトをみていてずっとうまくいかなかったのですが、Controllerのurlには注意してください。

https://jp1.api.riotgames.com/lol/summoner/v4/summoners/by-name/summoner_name.?api_key=[key]

この部分が参考サイトだと古いので必ず公式サイトを参照してください。(当時クローズドベータだったからだと予想)

あとがき

僕自身APIを利用すること自体が初めてなので、HTTPリクエストとかも完全に理解してないと思います。。。(勉強します)
なので、文章自体が少しおかしかったり、なんならviewはたぶんインデントとかひどいと思うので
もしそういうことがあればコメントいただけたらと思います。。。

champion.gg?そんなサイトはもう知らんなぁ!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモ PHP docker xdebug

何がしたい

dockerコンテナとしてnginxとphp-fpm動作させて、PhpStormを使ったxdebugによるリモートデバッグできるまでをやる

Overview

  • mac
  • dockerはdocker-for-mac
  • dockerコンテナ上でnginxが動作
  • dockerコンテナ上でphp-fpmが動作
    • with Xdebug
  • PhpStormでXdebugリモートデバッグをする

Source

https://github.com/tttreal/local-php-debug

HTTPリクエストの処理概要

  • nginxにport:80でリクエストが飛ぶ
  • nginxからphp-fpmにport:9000でリクエストが飛ぶ(9000はphp-fpmのデフォルト設定)
  • php-fpmはphpを動作させてレスポンスする
    • php上でxdebugが動作する (xdebug.remote_autostart=1 なので、勝手に動作)
    • php上のxdebugが、phpの処理中に、xdebug.remote_hostに対してDBGp接続(これなに?)を試みる
    • 公式の説明gif: https://xdebug.org/images/docs/dbgp-setup.gif
    • PhpStormがDBGpをうまくやっている・・・んだと思う(曖昧・・・?)
  • nginxがphp-fpmのレスポンスをクライアントに返す

(URLに?XDEBUG_SESSION_START=xxxとかつける必要はないよ)

docker

  • nginx用のコンテナ、php-fpm用のコンテナ を作成する
  • docker-for-macだと、"dockerのホストマシン"というのはmacではなく、mac上で動いている仮想環境
  • コンテナから見たmacは、docker.for.mac.host.internal
    • (ちなみにホストマシンはhost.docker.internal)
  • せっかくなので、docker-composeを使う

Xdebug

  • なんでかXdebugがデフォルトで使うport:9000と、php-fpmがデフォルトで使うport:9000とが、被っているので、どちらかを変える必要がある
    • なんでやねん
  • 設定例
xdebug.idekey="PHPSTORM"
xdebug.remote_enable=1
xdebug.remote_host=docker.for.mac.host.internal
xdebug.remote_port=9001
xdebug.remote_autostart=1
; xdebug.remote_connect_back=1

PhpStorm

よくわかんなかったんで適当にやった・・・

image.png

image.png

image.png

image.png

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モデリング以外のRDB論理設計 ~ ファイル編 ~

はじめに

 RDBを中核とする事務系システムでは、RDBの論理設計がシステムの「品質」「開発・保守・運用時の生産性」を大きく左右する。RDBの論理設計というとモデリングが重視されるが、モデリング以外にも重要なポイントがある。RDBの論理設計において、モデリング以外で注意すべきポイントを何回かに分けて説明してみたい。

 1回目はRDBでファイル(PDFや画像等のファイル)をどのように扱うべきか?を記してみたい。最初に断っておくが、ファイルというと「BLOB列でファイル内容を管理すべきか?」という議論があるが、この点については言及しない。

ファイルは属性を管理するテーブル、属性以外の情報を管理するテーブルの2種類で管理する。


属性を管理するテーブル

  • 属性を管理するテーブルは、次のようなイメージで定義する。
カ ラ ム データ型  属 性  内容
ID Number(8) 主キー サロゲートキー
SHA-1 Char(40) Not Null SHA-1ハッシュ値(16進数)
MD5 Char(32) Not Null MD5ハッシュ値(16進数)
サイズ Number(10) Not Null ファイルサイズ(バイト数)
リンク数 Number(8) Not Null 「属性以外の情報を管理するテーブル」の登録件数
MIME Varchar(255) Not Null 登録時にサーバー側で取得したMIME。
※ブラウザからファイルアップロード時、ブラウザからもMIMEは送信されてくるが、ブラウザから送信されてきたMIMEではなく、サーバー側でファイル内容をチェックしてMIMEを取得する。
拡張子 Varchar(10) Not Null MIMEから推測される拡張子
ファイル概要 Number(2) Not Null ファイルの概要。例えば
1:テキストファイル
2:PDFやエクセル等の事務系ファイル
3:画像ファイル
4:音声ファイル
5:動画ファイル
11:プログラム(ソースやスクリプト等のテキストファイル)
12:プログラム(exeやdll等のバイナリファイル)
99:アーカイブファイル
等のように使用
ファイル BLOB Not Null ファイル内容をBLOBで管理する場合のみ定義する。

(様々な属性)

(属性に応じたデータ型)
システムで扱うファイルの種類に応じた属性情報。
例えば画像を扱うのであれば解像度、
音声を扱うのであれば再生時間等を必要に応じて定義。
  • SHA-1、MD5、サイズの3列で複合ユニークキーを定義する。
  • 何らかのファイルを登録しようとする際、登録しようとするファイルのSHA-1、MD5、サイズの組み合わせで「属性を管理するテーブル」にレコードが登録されているかどうかをチェックする。存在しない場合は「属性を管理するテーブル」にInsertし、存在する場合はUpdate(リンク数のインクリメント)を行う。このことにより内容が同じファイルの重複登録を防ぐ。
  • 「拡張子」「ファイル概要」は「MIME」から特定できる情報であるため、正規化の観点からは不要な情報ではあるが、おそらく定義したほうが使い勝手が良いと思われるため、定義している。
  • ファイルをサーバー上のファイル名で管理する場合、ファイル名は「ID.拡張子」とする。保存ディレクトリはDBで管理すべきではない。(特定ディレクトリにファイルが集中することを避けて分散したい場合、例えばSHA-1の先頭1桁でサブディレクトリを作成し、その配下に保存する等の方式を検討する。)

属性以外の情報を管理するテーブル

  • 属性以外の情報を管理するテーブルは、次のようなイメージで定義する。
カ ラ ム データ型  属 性  内容
業務系要件に応じた主キー それに応じたデータ型 主キー 以下の例ではサロゲートキーとしている
ID Number(8) 外部キー 属性を管理するテーブルのID。
属性を管理するテーブルを親テーブルとする外部キーを定義する。
論理ファイル名 Varchar(255) Not Null 利用者が目にするファイル名

(要件に応じた様々な情報)

(情報に応じたデータ型)
業務系要件に応じた様々な情報。
例えば「ファイルのコメント情報」「ファイルの著作権情報」等を必要に応じて定義。

実装イメージ

  • これらのテーブルを用いた実装は次のイメージで行う。例として情報処理推進機構(IPA)が公開している「安全なウェブサイトの作り方」というPDFを、Aさんが「安全なウェブサイトの作り方.pdf」という名前で登録した後に、Bさんが「安全なウェブサイトの作り方(改訂第7版).pdf」という名前で登録したと仮定し、どのように処理するかを見てみる。

  • Aさんが「安全なウェブサイトの作り方.pdf」を登録した直後の「属性を管理するテーブル」主要項目(Insertされる)

カラム 設定内容
ID 51
SHA-1 7593c9741f96e5ee5c88f68abfdf8d0e68493d37
MD5 4cf49d7afe124c7e8e160a18e9453eff
サイズ 3855672
リンク数 1
MIME application/pdf; charset=binary
拡張子 pdf
  • Aさんが「安全なウェブサイトの作り方.pdf」を登録した直後の「属性以外の情報を管理するテーブル」主要項目(Insertされる)
カラム 設定内容
業務系要件に応じた主キー 200
ID 51
論理ファイル名 安全なウェブサイトの作り方.pdf
  • Bさんが「安全なウェブサイトの作り方.pdf」を登録した直後の「属性を管理するテーブル」主要項目(Updateされる。リンク数をインクリメントするだけ。)
カラム 設定内容
ID 51
リンク数 2
  • Bさんが「安全なウェブサイトの作り方.pdf」を登録した直後の「属性以外の情報を管理するテーブル」主要項目(Insertされる)
カラム 設定内容
業務系要件に応じた主キー 201
ID 51
論理ファイル名 安全なウェブサイトの作り方(改訂第7版).pdf

最後に

 この方式はハッシュの理解を前提に内容が同じファイルの重複登録を防ぐことを最大の目標としたものである。

  • ハッシュ値とは文字列やファイル等の「指紋やDNA」に相当する情報である。
  • ハッシュ値は固定長となる。(SHA-1は16進数で40桁、MD5は16進数で32桁の固定長となる。)
  • まれに衝突することがある。例にあげたPDFのSHA-1は 7593c9741f96e5ee5c88f68abfdf8d0e68493d37 であるが、これ以外のファイル(PDFとは限らない)であっても、同じ値となる可能性がある。但し、仮にSHA-1が別ファイルと同じ値になっても、MD5とファイルサイズも同じ値になる可能性は無い、というのが、この方式の大前提である。
  • ハッシュにはSHA-1、MD5以外の高精度な方式(衝突する可能性がより低い方式)も存在するが、SHA-1、MD5を選択したのは「代表的な方式であり、例えばPHPではライブラリの追加インストール等を行わずにsha1_file()md5_file()で使用できる」からである。(ちなみにPHPではhash_algos()でプラットフォーム上で使用可能なハッシュ方式が確認できる。)
  • ハッシュの説明を行ったので参考までに。一般的なセキュリティ環境下でネット上からダウンロードしたエクセル等のオフィス文書をエクセル等で開くと、保護モード(閲覧のみで編集が行えないモード)で表示されるが、編集を有効にする(保護モードを無効にする)と、ダウンロードしたエクセル等のオフィス文書のプロパティ情報が更新され、ハッシュ値も保護モード時とは異なるものになる。
  • ついでにもう一つ。パスワードの暗号化においてもハッシュは良く用いられるが、この場合「暗号化するパスワード」に「何らかの文字列(ソルトと呼ばれる)」を連結して暗号化することが望まれる。ソルトも固定文字列ではなく、ユーザーごとに異なり、初期登録後に変更されない情報(ユーザーIDや生年月日、入会年月日等)をもとに生成する方式が望ましい。(現在はSHA-1よりも推奨される方式があるので、プラットフォーム上で使用可能なハッシュ方式のなかで最善の方式を選択すべきなのは言うまでもない。)

 以上最後まで目を通して頂き、ありがとうございました。平成の終わりに10連休という長期休暇が得られたため、Qiita デビューに挑戦しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む