Laravelでポケモン図鑑をつくってAWSでデプロイまでしてみた


[株式会社やどかり](http://www.yadokari.tv/)のカイノと申します。
アドベントカレンダーを作ってみんなに書いてください〜って言ってやってもらってる張本人です。

今年もう少しで終わりだー今年もクソお世話になりましたー!

[やどかり&ネッコス Advent Calendar 2021](https://qiita.com/advent-calendar/2021/yadokari_qiita)の1日目を担当させていただきます。
明日は圭ちゃんです

# はじめに
2020年8月に入社して2021年6月までWeb開発をやってました。
2021年の7月からAndroid開発とWeb開発の2足のワラジでやってます。
今年はエンジニアとしてだけでなくマネジメント系の仕事もかなり増えててそっちもめちゃくちゃ面白いのですがやっぱりコード書いている時間は楽しいなとこの開発をしている時に改めて思いました。

去年も[似たような記事](https://qiita.com/kaino5454/items/e12a0214c76aea6cdc2b)を書いたのですが以前よりはレベルアップしているなー
、というか分かったこと増えたなーと感じました。
とはいえまだまだわからないところは多いので精進していきます。

ということで学んだことの棚卸しもしながら作りました

## 使用言語
Laravel 6.20
php 7.4

# ポケモン図鑑つくりました
http://13.115.177.152/

作ったことなかったので一度作りたかったんですよね。
仕事の合間に結構急ぎで作ったので色々と雑になってます。ドメインもまだ設定してないです。

# サイトはこんな感じです
### 一覧画面
レイアウトはテキトーです。
![スクリーンショット 2021-12-01 1.13.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574173/488ea21a-f740-17ba-4dc5-ead2c2d78c34.png)

### 詳細画面

![スクリーンショット 2021-12-01 1.18.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574173/fedd483a-92ed-c0f1-cf96-53fd0427c078.png)

フシギダネは最初に仲間にしたポケモンなのでめちゃめちゃ覚えてるし好きですね。
種族値も取れることを知ったので徹底攻略みたいに表示してみました。


# やったこと
 - Laravelでコマンド作成
 - ポケモンAPIを叩く
 - 取得したAPIを各パラメータに入れる
 - DB設計&作成
 - ポケモンごとにDBにインサート
 - 画面に表示
 - 種族値も表示してみた
 - AWSでサーバ構築してデプロイ

## Laravelでコマンド作成

app/Console/Commands/GetPokemonInfo.php
このpathに下記のようにコマンド作成
今回は`php artisan command:getpokemoninfo`で実行できるようにしました。

```php
<?php

namespace App\Console\Commands;

require "vendor/autoload.php";

use Illuminate\Console\Command;
use App\Models\Pokemon;
use GoogleTranslate\GoogleTranslate;

class GetPokemonInfo extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:getpokemoninfo';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        try {
            $p_no_array = range(1, 898);
            foreach ($p_no_array as $no) {
                // すでに取得済みの場合はスキップ
                $pokemon = Pokemon::where('p_id', $no)->first();
                if (!empty($pokemon)) {
                    continue;
                }
                // ポケモンごとの情報をcurlで取得
                $url = 'https://pokeapi.co/api/v2/pokemon/'.$no;
                $curl = curl_init($url);
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
                $r = curl_exec($curl);
                $data = json_decode($r, true);
                $p_info = self::getPokemonInfo($data);
                print_r($p_info['id']);
                print_r($p_info['jp_name']."\n");
                print_r($p_info['en_name']."\n");
                print_r("\n");
                $p_info = Pokemon::createPokemon($p_info);
                sleep(1);
            }
            curl_close($curl);

        } catch(Exception $e) {
            echo $e->getMessage();
        }
    }

    static function getPokemonInfo($d) {
        $p_info = [];
        // パラメータを設定
        $p_info['id'] = $d['id'];
        $p_info['en_name'] = $d['name'];
        $from = "en"; // English
        $to   = "ja"; // 日本語
        $st = new GoogleTranslate($p_info['en_name'], $from, $to);
        $p_info['jp_name'] = $st->exec();
        $p_info['type1'] = $d['types'][0]['type']['name'];
        if (isset($d['types'][1])) {
            $p_info['type2'] = $d['types'][1]['type']['name'];
        } else {
            $p_info['type2'] = null;
        }
        $p_info['ability1'] = $d['abilities'][0]['ability']['name'];
        if (isset($d['abilities'][1]) && !$d['abilities'][1]['is_hidden']) {
            $p_info['ability2'] = $d['abilities'][1]['ability']['name'];
            $p_info['hidden_ability'] = $d['abilities'][2]['ability']['name'];
        } else {
            $p_info['ability2'] = null;
            if (isset($d['abilities'][1]['is_hidden'])) {
                $p_info['hidden_ability'] = $d['abilities'][1]['ability']['name'];
            } else {
                $p_info['hidden_ability'] = null;
            }
        }
        $p_info['hp'] = $d['stats'][0]['base_stat'];
        $p_info['attack'] = $d['stats'][1]['base_stat'];
        $p_info['defense'] = $d['stats'][2]['base_stat'];
        $p_info['special_attack'] = $d['stats'][3]['base_stat'];
        $p_info['special_defense'] = $d['stats'][4]['base_stat'];
        $p_info['speed'] = $d['stats'][5]['base_stat'];
        $p_info['total_stats'] = $p_info['hp'] + $p_info['attack'] + $p_info['defense'] + $p_info['special_attack'] + $p_info['special_defense'] + $p_info['speed'];
        $p_info['front_default'] = $d['sprites']['front_default'];
        $p_info['back_default'] = $d['sprites']['back_default'];
        if (isset($d['sprites']['other']['dream_world'])) {
            $p_info['dream_world_front_default'] = $d['sprites']['other']['dream_world']['front_default'];
        } else {
            $p_info['dream_world_front_default'] = null;
        }
        if (isset($d['sprites']['other']['home'])) {
            $p_info['home_front_default'] = $d['sprites']['other']['home']['front_default'];
        } else {
            $p_info['home_front_default'] = null;
        }
        if (isset($d['sprites']['other']['official-artwork'])) {
            $p_info['official_artwork_front_default'] = $d['sprites']['other']['official-artwork']['front_default'];
        } else {
            $p_info['official_artwork_front_default'] = null;
        }
        $p_info['height'] = $d['height'];
        $p_info['weight'] = $d['weight'];

        return $p_info;
    }
}
```

### このコマンドでやりたいことは
`「ポケモンAPIから取得した各ポケモン情報をパラメータに格納してDBにインサートさせる」`ことです。

### ポケモンAPIを叩く

```php
            $p_no_array = range(1, 898);
            foreach ($p_no_array as $no) {
                // すでに取得済みの場合はスキップ
                $pokemon = Pokemon::where('p_id', $no)->first();
                if (!empty($pokemon)) {
                    continue;
                }
                // ポケモンごとの情報をcurlで取得
                $url = 'https://pokeapi.co/api/v2/pokemon/'.$no;
                $curl = curl_init($url);
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
                $r = curl_exec($curl);
                $data = json_decode($r, true);
                $p_info = self::getPokemonInfo($data);
                print_r($p_info['id']);
                print_r($p_info['jp_name']."\n");
                print_r($p_info['en_name']."\n");
                print_r("\n");
                $p_info = Pokemon::createPokemon($p_info);
                sleep(1);
            }
            curl_close($curl);

```

ポケモンAPIのurlにcurlでアクセスして受け取ったJSONをデコードしてます。
これを1~898回繰り返します。
図鑑No.1のフシギダネからNo.898バドレックスまでです。

サーバに攻撃にならないようにsleep()をいれて間隔を少しだけ空けてます。

### 取得したAPIを各パラメータに入れる

getPokemonInfoというfunctionを呼び出して
各パラメータの中にポケモン情報を格納しています。

https://pokeapi.co/api/v2/pokemon/1
を実行してみるとわかるのですが情報がめちゃめちゃあってどれを使おうかを決めて各パラメータにいれねばなりません。
地味な作業でした。。。

```php
    static function getPokemonInfo($d) {
        $p_info = [];
        // パラメータを設定
        $p_info['id'] = $d['id'];
        $p_info['en_name'] = $d['name'];
        $from = "en"; // English
        $to   = "ja"; // 日本語
        $st = new GoogleTranslate($p_info['en_name'], $from, $to);
        $p_info['jp_name'] = $st->exec();
        $p_info['type1'] = $d['types'][0]['type']['name'];
        if (isset($d['types'][1])) {
            $p_info['type2'] = $d['types'][1]['type']['name'];
        } else {
            $p_info['type2'] = null;
        }
        $p_info['ability1'] = $d['abilities'][0]['ability']['name'];
        if (isset($d['abilities'][1]) && !$d['abilities'][1]['is_hidden']) {
            $p_info['ability2'] = $d['abilities'][1]['ability']['name'];
            $p_info['hidden_ability'] = $d['abilities'][2]['ability']['name'];
        } else {
            $p_info['ability2'] = null;
            if (isset($d['abilities'][1]['is_hidden'])) {
                $p_info['hidden_ability'] = $d['abilities'][1]['ability']['name'];
            } else {
                $p_info['hidden_ability'] = null;
            }
        }
        $p_info['hp'] = $d['stats'][0]['base_stat'];
        $p_info['attack'] = $d['stats'][1]['base_stat'];
        $p_info['defense'] = $d['stats'][2]['base_stat'];
        $p_info['special_attack'] = $d['stats'][3]['base_stat'];
        $p_info['special_defense'] = $d['stats'][4]['base_stat'];
        $p_info['speed'] = $d['stats'][5]['base_stat'];
        $p_info['total_stats'] = $p_info['hp'] + $p_info['attack'] + $p_info['defense'] + $p_info['special_attack'] + $p_info['special_defense'] + $p_info['speed'];
        $p_info['front_default'] = $d['sprites']['front_default'];
        $p_info['back_default'] = $d['sprites']['back_default'];
        if (isset($d['sprites']['other']['dream_world'])) {
            $p_info['dream_world_front_default'] = $d['sprites']['other']['dream_world']['front_default'];
        } else {
            $p_info['dream_world_front_default'] = null;
        }
        if (isset($d['sprites']['other']['home'])) {
            $p_info['home_front_default'] = $d['sprites']['other']['home']['front_default'];
        } else {
            $p_info['home_front_default'] = null;
        }
        if (isset($d['sprites']['other']['official-artwork'])) {
            $p_info['official_artwork_front_default'] = $d['sprites']['other']['official-artwork']['front_default'];
        } else {
            $p_info['official_artwork_front_default'] = null;
        }
        $p_info['height'] = $d['height'];
        $p_info['weight'] = $d['weight'];

        return $p_info;
    }
```

### DB設計&作成

今回はpokemonテーブルのみ作りました。
Laravelのマイグレーションで作っています。

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePokemonsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('pokemons', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('p_id')->nullable();
            $table->string('jp_name')->nullable();
            $table->string('en_name')->nullable();
            $table->string('type1')->nullable();
            $table->string('type2')->nullable();
            $table->string('ability1')->nullable();
            $table->string('ability2')->nullable();
            $table->string('hidden_ability')->nullable();
            $table->integer('hp')->nullable();
            $table->integer('attack')->nullable();
            $table->integer('defense')->nullable();
            $table->integer('special_attack')->nullable();
            $table->integer('special_defense')->nullable();
            $table->integer('speed')->nullable();
            $table->integer('total_stats')->nullable();
            $table->string('front_default')->nullable();
            $table->string('back_default')->nullable();
            $table->string('dream_world_front_default')->nullable();
            $table->string('home_front_default')->nullable();
            $table->string('official_artwork_front_default')->nullable();
            $table->integer('height')->nullable();
            $table->integer('weight')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('pokemons');
    }
}

```

### ポケモンごとにDBにインサート

インサートするファンクションをModel内で作成しました。
ここはシンプル。

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Pokemon extends Model
{
    protected $table = 'pokemons';
    protected $dates = ['publication_date', 'expiration_date', 'registration_date'];
    protected $fillable = [
        'p_id', 'jp_name', 'en_name', 'type1', 'type2', 'ability1', 'ability2', 'hidden_ability',
        'hp', 'attack', 'defense', 'special_attack', 'special_defense','speed',
        'total_stats', 'front_default', 'back_default', 'dream_world_front_default',
        'home_front_default', 'official_artwork_front_default', 'height', 'weight'
    ];

    static function createPokemon($p_info) {
        if (empty($pokemon)) {
            self::create([
                'p_id'                           => $p_info['id'],
                'jp_name'                        => $p_info['jp_name'],
                'en_name'                        => $p_info['en_name'],
                'type1'                          => $p_info['type1'],
                'type2'                          => $p_info['type2'],
                'ability1'                       => $p_info['ability1'],
                'ability2'                       => $p_info['ability2'],
                'hidden_ability'                 => $p_info['hidden_ability'],
                'hp'                             => $p_info['hp'],
                'attack'                         => $p_info['attack'],
                'defense'                        => $p_info['defense'],
                'special_attack'                 => $p_info['special_attack'],
                'special_defense'                => $p_info['special_defense'],
                'speed'                          => $p_info['speed'],
                'total_stats'                    => $p_info['total_stats'],
                'front_default'                  => $p_info['front_default'],
                'back_default'                   => $p_info['back_default'],
                'dream_world_front_default'      => $p_info['dream_world_front_default'],
                'home_front_default'             => $p_info['home_front_default'],
                'official_artwork_front_default' => $p_info['official_artwork_front_default'],
                'height'                         => $p_info['height'],
                'weight'                         => $p_info['weight']
            ]);
        }
        return true;
    }
}

```


### 画面に表示

```php
<div id="pokemon_block">
	<div class="wrap-inner">
		<ul class="poke_list flexlist">
			<?php foreach ($pokemons as $pokemon) : ?>
				<li class="item">
					<a href="{{ route('poke_show', $pokemon['id']) }}">
						No.<?php echo $pokemon['id'] ?>
						<?php echo $pokemon['jp_name'] ?>
						<div class="imgarea">
							<img src="<?php echo $pokemon['front_default'] ?>" alt="">
						</div>
					</a>
				</li>
			<?php endforeach ?>
		</ul>
	</div>
</div>
```

一旦全部のポケモンを表示させました。
'front_default'は画質が低いので大きな遅延はないのですが、他の重めの画像だと表示までに30秒くらいはかかってました。

今回は'front_default'を選んで遅延がないようにしました。
ただ、すこーしだけ遅いので全件表示よりは各世代ごとに分けて出すとかにする方が現実的ですね。



### 種族値も表示してみた

```php
<?php for($i = 0; $i < $pokemon['hp']/10; $i++): ?>
	<span class="single_gauge">▓</span>
<?php endfor ?>
```

"▓"という文字列を種族値/10の回数分だけ表示するように出しています。
これでこのポケモンの種族値の高さざっくりと見やすくなりましたね。

![スクリーンショット 2021-12-01 9.22.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574173/58b50354-a941-224d-75ff-bbaa59825ffb.png)

さてはこの種族値は何でしょう？

↓下のリンクが答えです。

### AWSでサーバ構築してデプロイ
http://13.115.177.152/pokemon_show/445

サーバ構築はシンプルにしています。
・ec2 linux/apache
・EIP
・RDS

## ハッサムが好き！
かっこいい系のポケモンが好きなんですよね。

ハッサムはアルファサファイアやってた時にサイクルパでめっちゃ使ってましたね~
使いやすいしかっこいいし（語彙力）

![スクリーンショット 2021-12-01 1.18.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574173/021b9e28-4df8-15ea-1e6d-89bf7ea442dc.png)


# おわりに
これで終わりです。

もっと色々やりたかったのですが、とりあえずここまでにしました。
今回でポケモンDBを作れたので今後色々なアプリに使えるなーと思ってます。
勉強がてら遊びがてら使っていこうと思います。

今年も仕事納め前で絶賛忙殺中な毎日ですがわずかな合間を縫って10時間以内で全部作りました。
0から開発してサーバにアップするまでのスピード感も少しずつ付いてきた気がします。