- 投稿日:2021-03-15T23:33:15+09:00
Laravel+Guzzleを使用してAPIトークンを取得する方法
こんにちは、くりぱんです。
この記事で実現できること
- Guzzleのインストール
- Laravel+GuzzleでAPIトークンの取得
説明
今回はPHPのHTTPクライアントであるGuzzleを使用して、とあるAPIのトークンを取得していきたいと思います。
開発環境
- Laravel: 6.2
- Guzzule: 7.2
実装の流れ
- Laravelプロジェクトの作成
- Guzzleのインストール
- 日本時間にする
- データベース設定
- Route
- Controller
- View
実装
1. Laravelのプロジェクトの作成
$ composer create-project --prefer-dist laravel/laravel testAPI "6.*"
testAPIというLaravel6のプロジェクトが作成されます。2. Guzzleのインストール
$ composer require guzzlehttp/guzzlecomposer.jsonで確認していきます。
composer.json{ ーー省略ーー "license": "MIT", "require": { "php": "^7.2.5|^8.0", "fideloper/proxy": "^4.4", "guzzlehttp/guzzle": "^7.2", // ここチェック "laravel/framework": "^6.20", "laravel/tinker": "^2.5" }, ーー省略ーー }私の場合composer.jsonには
"guzzlehttp/guzzle": "^7.2"となっていますが、バージョンが多少異なっていても特に問題はありません。3. 日本時間にする
APIトークンの有効期限も画面に出したいので、一応日本時間にしておきます。
config/app.phpを編集します。app.phpreturn [ ーー省略ーー 'timezone' => 'Asia/Tokyo', ーー省略ーー ]4. データベース設定
今回はMySQLを使用していきます。
mysql -u root -pなどでMySQLに入り、test_apiというデータベースを作成してください。mysql> CREATE DATABASE test_api;
.envに作成したデータベースの設定をしていきます。.env// データベース関連のところを抜粋 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=test_api // 今回作成したデータベース名 DB_USERNAME=ユーザー名 DB_PASSWORD=パスワード5. Route
routes/web.phpでルートの設定を行っていきます。web.php<?php Route::get('/', 'TestApiController@index'); // ここを追記
http://127.0.0.1:8000/にアクセスするとこれから作成するTestApiControllerのindexメソッドが走るようになりました。6. Controller
$ php artisan make:controller TestApiControllerこのコマンドで、以下のような
app/Http/Controllers/testApiController.phpが作成されます。<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class TestApiController extends Controller { // }今回は例として下記の情報で接続したと過程します。
- ベースURL:
openapi.test.api.jp- HTTP リクエスト:
POST openapi.test.api.jp/v2/test- API_KEY:
hjfdksaJhjfkdw134fjdklsaKJ93JKL- user_id:
user_id- password:
password先程コマンドで作成した
app/Http/Controllers/testApiController.phpを編集していきます。TestApiController.php<?php namespace App\Http\Controllers; use Facade\Ignition\QueryRecorder\Query; use Illuminate\Http\Request; use GuzzleHttp\Client; use GuzzleHttp\Psr7\Header; class TestApiController extends Controller { public function index() { // ベースURL $base_url = 'openapi.test.api.jp'; // インスタンス作成 $client = new Client([ 'base_url' => $base_url, ]); // API_KEY $api_key = 'hjfdksaJhjfkdw134fjdklsaKJ93JKL'; // オプション $options = [ // デバック(デバックしたい時は記述) 'debug' => true, // // パラメーター(Header) 'headers' => [ 'api-key' => $api_key, ], // パラメーター(Query) 'query' => [ 'user_id' => 'user_id', ], // パラメーター(Body) 'json' => [ 'password' => 'password', ], ]; // パス $path = '/v2/test'; // リクエストするURL(openapi.test.api.jp/v2/test) $send_url = $base_url . $path; $response = $client->request('POST', $send_url, $options); // JSONデータとして取得 $json = $response->getBody(); // JSONデータを連想配列にする $api_token = json_decode($json, true); return view('welcome', compact('api_token')); } }passwordはTypeがBodyだったのですが、json指定だとうまい感じで変換してくれます。
7. View
JSONを連想配列で取得しているので、有効期限とAPIトークンをそれぞれ取得していきます。
resources/views/welcome.blade.phpを下記の通り編集してください。welcome.blade.php<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Laravel</title> </head> <body> <p>有効期限:{{ date('Y年m月d日 H時i分s秒', $api_token['expires_at']) }}</p> <p>APIトークン:{{ $api_token['token'] }}</p> </body> </html>有効期限はミリ秒で取得されるので、date関数でフォーマットを整えています。
これで、php artisan serveしてからブラウザで確かめると、無事有効期限とAPIトークンを取得できるかと思います。最後に
APIはまだまだわからないことばかりなので、ぜひご指摘等ありましたらコメント欄にお願いいたします。
少しでも役に立った!という時は、LGTMをポチッと、、、笑
1つでもLGTMが付くとその日がハッピーになるんです!
役に立たなかった時は、怒らないでコメント頂けると幸いです笑Twitterもやってます!
プログラミングや金融知識についてやエンジニアの現実についてつぶやいています!
よかったら見てみてくださいね!
- 投稿日:2021-03-15T22:42:31+09:00
ridgepoleでfulltext indexを貼る方法
MySQLの全文検索インデックス
MySQL5.6から、
fulltextインデックスを貼ることができるようになった。(pluginによるparserの変更は5.7から?)ridgepoleでfulltext indexを貼ると発生する問題
create_table :fuga do ... t.text :hoge ... end execute("CREATE FULLTEXT INDEX fk_hoge ON fuga(hoge) WITH PARSER ngram")この書き方だと、冪等性がなく、
2回目以降に実行すると、一度、remote_index :hogeが行われたあとに、再度、executeのCREATE FULLTEXT INDEXが呼ばれてしまう。create_table句でnameに対するindexを貼っていないための模様この状態だと、マイグレーションを走らせるたびに、重いINDEXを貼る作業が毎回行われるので、回避するべき事象となる。
対処方法
create_table :fuga do ... t.text :hoge t.index :hoge, type: :fulltext, ignore: true # これを追加 ... end execute("CREATE FULLTEXT INDEX fk_hoge ON fuga(hoge) WITH PARSER ngram") do |c| # INDEXが登録済みの場合は再実行しない rows = c.raw_connection.query("SHOW INDEX FROM fuga") rows.none? { _1[2] == 'fk_hoge' } end
ignoreを含んだ、TableDefinitionを追加し、executeにINDEXの存在を確認するブロックを渡すことで、再実行してもOKになる。
超参考
- 投稿日:2021-03-15T21:22:16+09:00
PHP/MySQLで作ったポートフォリオの振り返り
初めに
開発環境はmacOSで
PHPのバージョンは7.3を使用しています。
あくまで、アウトプットを目的として書いた記事なので、理解しにくい点、間違った内容があるかもしれません。ご容赦ください。ポートフォリオについて
名前は『BookList』というアプリで、本のECサイトという設定で開発しました。
ECサイトは、CRUD機能だけでなく、より便利に理想的なものを作ろうとすると、
たくさんの機能が追加でき、すでに世の中にお手本となるフリマサイト、ECサイトがたくさんあるので自分もECサイトの開発に挑戦してみたいと思いました。アプリの機能一覧
No 機能 機能について 1 新規登録機能 2 ログイン機能 3 ゲストログイン機能 新規登録しなくても、予め用意されているユーザー 4 パスワードのハッシュ化 この機能は記事にまとめました 5 商品の出品機能 商品の状態、種類、画像、タイトル、値段、著者などを登録 6 商品の編集 出品した、個数、値段、PR文、ステータスなどを編集 7 商品の削除 出品者は自分の商品を削除 8 コメント機能 商品の購入前に、出品者に対してコメントできる 9 コメント削除機能 10 商品の検索機能 商品の状態、種類で商品を検索できる 11 ページネーション 商品一覧ページ、商品別コメントページにそれぞれ 12 カートに追加 商品をカートに追加 13 カート削除 カートに追加した商品を削除 14 商品購入機能 商品を購入する ECサイトで必須となる、出品->カートに追加->購入機能を中心に、本を扱うアプリなので、本の状態や、著者、コメント機能なども、追加していきました。
DB設計
カラムは
idしか表示させていませんが、DB設計を通じて、主キーや外部キーの繋がりを理解し、論理的に開発することができました。セキュリティ、脆弱性
このポートフォリオは、フレームワークを使わずに、生のPHPとMysqlのみで作ったので、セキュリティ、脆弱性についてはしっかりと対策しました。
クロスサイトスクリプティング(XSS)
//xss対策 function h($str){ return htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); }上記の関数を作り、画面上に
echo printを使って、値を出力する時は必ずh(hogehoge)とエスケープ処理をしました。SQLインジェクション
これに関しては、
formからpostされてきた値をそのまま、保存するのではなく、
変数に格納し、PDOのprepareメソッドを利用して作成したステートメントに値をバインドさせ対策しました。クロスサイトリクエストフォージェリ (CSRF)
主な脆弱性の一つの
csrfに対しては、post送信を行う全てのformに対して、ランダムな文字列を生成し、post送信の受け取って操作をする前に、そのトークンの照合行い、異なる文字列の場合には、ログインページに飛ばすように処理しました。
トークンの文字列が盗まれ、再利用されないように、トークンの破棄も忘れず、実装しています。工夫した点
1.MVCを意識する
フレームワークを使っていなくても
model、view、controllerとそれぞれのフォルダを作り、データの流れを意識しながら、開発を進めました。2.高品質なコーディングを心がける
なるべくわかりやすい関数名、変数名を命名し、
require,includeなどの関数を用いて、templeteファイルなどで共通化を取り入れました。さらにコメントを積極的に、コード内に書き込み可読性の高いコーディングに仕上げました。参考 github
デプロイについて
conohaのvpsを使って、デプロイしました。
ssh接続し、本番環境にファイル、データベースの設置が完了したら、
セキュリティを高めるために、rootログインを禁止にし公開鍵認証の設置、filewallの設定でポート番号を変更しました。
そして、ドメインを購入して、conohaのIPアドレスに割り当てました。課題
デザイン面
バックエンドエンジニアを目指しているので、最低限のデザインしかしていない
DBの発行のしすぎ
重複したDBの値などもあり、処理が増えてしまっている
まとめ
・フレームワークを使っていないので、多機能なアプリとは言えないが、あえてPHPのみでの開発でプログラミングの楽しさや、エラーが発生した時の自走力が身につきました。
・セキュリティに関する知識、対策、SQLの理解なども深めることができました。フレームワークを利用すると、あまり直接SQL文を書くことがないのでとても勉強になったし、汎用性のきく知識だと感じています。
最後まで、読んでいただきありがとうございました!!
- 投稿日:2021-03-15T20:46:31+09:00
Docker,PHP,MySQLで構築した環境におけるDBホスト名の調べ方
以下の記事を参考に、Docker,PHP,MySQLの開発環境を構築した。
記事の手順にしたがって開発環境は構築できたのだが、mysqliでDBに接続する際に、ホスト名が分からない事態に遭遇。
ホスト名は、DBに接続して以下のコマンドを入力する事で確認できる。
mysql> show variables like 'hostname';
今回はDocker環境なので、DBコンテナに入ってから上記コマンドを入力する必要がある。
入力するコマンドは以下の通り。// DBコンテナに接続 docker-compose exec db bash // DBに接続 root@2918fd6815ab:/# mysql -u root -p → パスワードを入力 // ホスト名を表示 mysql> show variables like 'hostname';以上のコマンドで、ホスト名を確認できる。
- 投稿日:2021-03-15T18:14:41+09:00
MAMPでEC-CUBE2系インストールに失敗した
MAMPで、EC-CUBE2系(2.13.5)をLocalにインストールした際、DB情報入力画面の次で処理が止まるエラーが発現。
何度もインストールしてたのに、解消まで手間取ったのでログを残す。ログを確認すると、
[15-Mar-2021 16:53:16 Asia/Tokyo] PHP Fatal error: DB処理でエラーが発生しました。
SQL: [SET SESSION storage_engine = InnoDB]
MDB2 Error: unknown error
_doQuery: [Error message: Could not execute statement]
[Last executed query: PREPARE mdb2_statement_mysql_12a941c4181cfb45bc8b89a459f857c1ba510fac2 FROM 'SET SESSION storage_engine = InnoDB']
[Native code: 1193]
[Native message: Unknown system variable 'storage_engine']
in /Applications/MAMP/htdocs/eccube-2.13.5/data/class/SC_Query.php on line 1095とのこと。storage engineのエラーとのことなので、MySQLそのものが壊れちゃったのかと疑った。
しかし、結論からいうと、下記の記事が解決法になりました。
前のMAMPではMySQLのバージョンが5.7よりも低かったのでしょうね。おそらく。
今回はEC-CUBEのソース側を変更することでインストール処理を進めました。
終わり。
- 投稿日:2021-03-15T14:13:27+09:00
SpringBoot + MySQL でTodoリストを作る!CRUDの実装(Read編)
はじめに
今回は、「SpringBootでMySQLからデータを取得して、Thymeleafを用いて画面に表示させる」をゴールとしています。
SpringBootを学び始めた方、復習をしたい方に向けて学んだ事を共有します。
開発環境
OS: macOS Mojave(バージョン 10.14.6)$ java -version openjdk version "11.0.2" 2019-01-15 OpenJDK Runtime Environment 18.9 (build 11.0.2+9) OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)$ mvn -version Maven home: /usr/local/Cellar/maven/3.6.3_1/libexec Java version: 15.0.2, vendor: N/A, runtime: /usr/local/Cellar/openjdk/15.0.2/libexec/openjdk.jdk/Contents/Home Default locale: ja_JP, platform encoding: UTF-8 OS name: "mac os x", version: "10.14.6", arch: "x86_64", family: "mac"$ spring --version Spring CLI v2.3.1.RELEASE$ mysql --version mysql Ver 8.0.23 for osx10.14 on x86_64 (Homebrew)プロジェクトの作成(pom.xmlの編集)
Spring initializr でサクッとプロジェクトを作成します。
依存関係は以下の通りです。
pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>todolist</artifactId> <version>0.0.1-SNAPSHOT</version> <name>todolist</name> <description>Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <!-- テンプレートエンジン --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- RESTfulを含んだWebアプリケーションライブラリの利用 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- ファイルの変更を検知すると自動で再起動する --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- @Dataアノテーションを付与する事で,getter,setter等を自動生成してくれる --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- テスト用 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- DBとJavaオブジェクトやり取りを行う。 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>MySQLと接続
いきなりターミナルで
mvn spring-boot:runを打ち込んで起動しようとしても出来ません。terminal$ mvn spring-boot:run ~以下略~ *************************** APPLICATION FAILED TO START *************************** Description: Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. Reason: Failed to determine a suitable driver class1.DBを作成、テストデータの作成
まずMySQLでデータベースを作成します。
データベース作成CREATE DATABASE spring_todolist_qiita;mysqlmysql> show databases; +-----------------------+ | Database | +-----------------------+ | information_schema | | mysql | | performance_schema | | spring_todolist_qiita | -- ←作成完了 | sys | +-----------------------+テーブルを作成してデータを追加します。
テーブル作成mysql> use spring_todolist_qiita; mysql> create table if not exists todolist ( id integer not null auto_increment, content text not null, done boolean not null default 0, primary key (id) );データを何個か作っておきます。
データの追加mysql> insert into todolist (content) values ("卵を買う"); --以下略--2.SpringBootにMySQLの情報を定義する
application.propertiesにMySQLに接続するため記述します。
application.properties# 接続先のDB名を設定 spring.datasource.url=jdbc:mysql://localhost/spring_todolist_qiita # 接続しているDBの username と passwordを設定 # 〇〇と△△はご自身のusername,passwordを記載してください spring.datasource.username=〇〇 spring.datasource.password=△△ # MySQL用JDBCドライバの設定 spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver再度ターミナルで
mvn spring-boot:runを打ち込むと、起動はするようになります。SpringBootとMySQL間のやり取りの実装
TodolistApplication.javaと同じ階層に各フォルダを作成して、それぞれ中身を実装していきます。
1.エンティティモデルの作成
@Entityでエンティティクラスである事を宣言し、データベースのテーブルとマッピング。
@Table(name = "todolist")は、エンティティクラスとマッピングされるDBのテーブル名を指定する。
@Idは、主キーである事を、@GeneratedValue(strategy = GenerationType.IDENTITY)は、主キーが自動採番されるよう宣言。Todo.javapackage com.example.todolist.Model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import lombok.Data; // データを格納するクラス(DBへ登録、更新時の入れ物) @Entity @Data @Table(name = "todolist") public class Todo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String content; private boolean done; }2.DBにアクセスするRepositoryを作成
データベースから値の取得、更新などのメソッドが定義されている
JpaRepositoryインターフェースを継承したインターフェースを用意する。
@RepositoryでDBアクセスのクラスであると宣言、DIコンテナに登録される。TodoRepository.javapackage com.example.todolist.Repository; import org.springframework.stereotype.Repository; import org.springframework.data.jpa.repository.JpaRepository; import com.example.todolist.Model.Todo; // DBにアクセスするためのインターフェース @Repository public interface TodoRepository extends JpaRepository<Todo, Integer> { }3.具体的な処理を行うServiceを作成
@Serviceでサービスクラスである事を宣言、DIコンテナに登録される。
@Autowiredで、DIコンテナに登録されているTodoRepositoryのインスタンスを生成して返してくれている。TodoService.javapackage com.example.todolist.Service; import java.util.List; import com.example.todolist.Model.Todo; import com.example.todolist.Repository.TodoRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; // DBとの具体的な処理(データの取得、新規作成など)を記述するクラス @Service public class TodoService { @Autowired TodoRepository todoRepository; // todolistを全件取得 public List<Todo> searchAll() { return todoRepository.findAll(); } }取得したデータを画面に表示させる
Controllerを用意してDBからデータを取得し、Viewに送って画面に表示させます。
1.Controllerを作成
@Controllerでコントローラークラスである事を宣言。
@Autowiredで、DIコンテナに登録されているTodoServiceのインスタンスを生成して返してくれている。TodoController.javapackage com.example.todolist.Controller; import java.util.List; import com.example.todolist.Model.Todo; import com.example.todolist.Service.TodoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @Controller public class TodoController { @Autowired TodoService todoService; @GetMapping("/") public String home(Model model) { List<Todo> allTodo = todoService.searchAll(); model.addAttribute("allTodo", allTodo); return "home"; } }
@GetMapping("/")でlocalhost:8080にアクセスすると、homeメソッドが呼び出される。
todoServiceのserarchAllメソッドで、DBからデータを全件取得してmodel.addAttributeでViewにJavaオブジェクトを渡している。2.HTMLの作成
上記Controllerで
return "home"としているので、home.htmlを作成する。home.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>SpringTodoList</title> </head> <body> <div th:each="todo : ${allTodo}"> <p th:text="${todo.content}"></p> </div> </body> </html>Controllerから渡された
allTodoはList型なので、
th:each="todo : ${allTodo}"のループを回してtodoに一つずつデータを格納している。
todo.contentでcontentを描画している。SpringBootの起動
MySQLのデータが画面に表示されているか確認します。
terminal$ mvn spring-boot:runlocalhost:8080にアクセスしてみると...
無事表示されました。
終わりに
CSSの記述もしていないので味気ないページですが、SpringBootとMySQLを接続してデータを表示させる事が出来ました。
ご自身の環境でも試してみてください。
次は新規登録の実装をしてみたいと思います。
- 投稿日:2021-03-15T12:18:51+09:00
<Golang+Nginx+MySQL>Macローカル上にDocker Composeでアプリを立ち上げてみた
Dockerアプリケーションのアーキテクチャー
ざっくりとやること・できることの説明
- docker-composeでNginxとGo言語のAPI, DBとしてMySQLのコンテナを3つ立ち上げる
- Nginxのindex.htmlからGo言語で書いたREST API(go-ginのフレームワーク使用)を経由して、DBに問い合わせ
- DBにCRUD(gormのフレームワーク使用)ができることを確認
- ※DockerとDocker Composeはインストールされているものとする。
バージョン確認
確認してみたら、以下のバージョンでした。
Docker version 20.10.2 docker-compose version 1.27.4 golang:1.14.7-alpine3.12 mysql:5.7 nginx:1.17.3-alpine※厳密にはバージョンの依存関係とかも考えないといけないが、一旦ローカルで動かすので、スルーしますm(__)m
フォルダ構成
docker-local ├── .env ├── api │ └── src │ ├── main.go │ ├── model ── model.go │ └── config ── config.go ├── db │ ├── conf ── my.cnf │ └── db-data ├── docker │ └── nginx │ ├── Dockerfile │ └── nginx.conf ├── docker-compose.yml └── web ├── env │ └── nginx ── site.conf └ src ── index.htmlディレクトリとファイルの用途解説
名前 種類 説明 docker-local フォルダ dockerローカルアプリケーションの親フォルダ .env ファイル docker-composeで使う環境変数のファイル api フォルダ APIのフォルダ src フォルダ ソースフォルダ main.go ファイル goを起動するときのファイル model/model.go ファイル DBとやりとりする時のファイル
struct, dao系のメソッドを記載config/config.go ファイル DBの接続情報等を記載 db フォルダ DBに関するフォルダ conf/my.cnf ファイル 任意のDB設定を記載。マウント用 db-data フォルダ DBのデータをsyncするマウント用 docker フォルダ Dockerfileたちを格納するフォルダ nginx フォルダ NginxのimageをビルドするDockerfileを格納 Dockerfile ファイル nginxのimage nginx.conf ファイル nginxの設定ファイル docker-compose.yml ファイル docker containerを一括でコントロールするyamlファイル web フォルダ フロントのファイルを格納 env フォルダ 環境設定ファイルを格納 nginx/site.conf ファイル nginxのserver設定ファイル
IPアドレス、ポート、ルーティングなど設定src/index.html ファイル 画面表示用ファイル Docker Composeの中身
version: "3.8" services: nginx: image: infra-challenge:nginx-20210312 # command: build: context: ./docker/nginx dockerfile: Dockerfile container_name: nginx ports: - 80:80 volumes: - ./web/env/nginx:/etc/nginx/conf.d - ./web/src:/var/www/html depends_on: - api logging: driver: "none" restart: always networks: - services api: image: golang:1.14.7-alpine3.12 command: go run main.go --host 0.0.0.0 --port 8080 container_name: api ports: - 8080:8080 volumes: - ./api:/go/src/github.com/infra-challenge/api - /Users/username/go:/go working_dir: /go/src/github.com/infra-challenge/api/src environment: - APP_STAGE=local - LOG_LEVEL=debug - MYSQL_CONNECTION depends_on: - db restart: always networks: - services db: image: mysql:5.7 # build: ./docker/mysql container_name: db ports: - 3306:3306 volumes: - ./db/conf/my.cnf:/etc/mysql/conf.d/my.cnf - ./db/db-data:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD - MYSQL_DATABASE - MYSQL_USER - MYSQL_PASSWORD healthcheck: test: ["CMD-SHELL", "mysqlshow -u ${MYSQL_USER} -p${MYSQL_PASSWORD}"] interval: "5s" retries: 10 logging: driver: "none" restart: always networks: - services networks: services: external: name: local_infra_challenge_networks主な属性の解説
docker composeの文法バージョンは3.8だよservices配下に、コンテナの定義を書いていく。今回はnginx, api, dbの3つnginxはDockerfileを使うから、image名は任意で、buildの属性を指定。
contextはDockerfileのある相対パス、dockerfileはDockerfile名- ※ファイル名が
Dockerfileとなっていたら、build: ./docker/nginxとしてもOKcontainer_nameを指定すると、docker stopとかするときに、idじゃなくて名前を指定すれば制御できるので便利portsは<ホスト(Macローカル)>:<ゲスト(Dockerコンテナ)>でポートを接続volumesは<ホスト(Macローカル)>:<ゲスト(Dockerコンテナ)>でマウントフォルダを指定
- マウントとは、フォルダ同士を共有してsync(同期)すること。
depends_onは他のコンテナが起動成功でき次第〜みたいなやつ- apiとdbは、自分でDockerfileは用意せずに、
Docker-Hub公式のimageを使っているenvironmentに環境変数を指定する。
docker-compose.ymlと同階層に.envファイルを置き指定- DBでは
healthcheckを行い、起動しているか随時確認- ネットワークを組むことで、コンテナ間通信が可能となり、APIからDBに問い合わせができるようになった
ネットワークについて
作ったネットワークの情報を確認してみると、下記のようなObjectが返ってきますね〜
"Subnet": "172.19.0.0/16",の中で、Containers(nginx, api, db)がIPアドレスを振られて存在しているのがわかります。
ちなみに、networksだけでなくlinksという属性もあり、linksを使うと、一方的にコンテナ間通信ができるようになる設定もあるので、TPOによって使い分ける◎
公式docker:links$ docker network inspect local_infra_challenge_networks [ { "Name": "local_infra_challenge_networks", "Id": "f6a978e6f3aa803be061418c1c7081d132c9538c207ea08b1d732fc55ba2b917", "Created": "2021-03-12T03:57:20.0697997Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "172.19.0.0/16", "Gateway": "172.19.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "7a81d8c6f965a07ce446aa0983180bbb4775187ed6873a9e26dbc9540f623392": { "Name": "nginx", "EndpointID": "09336c1acc58f9b75ea3ced604a7d82b168a5f0cd132ae4f2fffd267a416b022", "MacAddress": "02:42:ac:13:00:04", "IPv4Address": "172.19.0.4/16", #<-これ!! "IPv6Address": "" }, "842244244bf00593e25317dd1cafb35ae03cfbc5246a87c666c6434a6ad7540d": { "Name": "db", "EndpointID": "3810c5b1b5f87b3ba4331320fcb2106696f3dcd157a771f9de63aeffc3a14720", "MacAddress": "02:42:ac:13:00:02", "IPv4Address": "172.19.0.2/16", #<-これ!! "IPv6Address": "" }, "c0906b3fc86d92f4b89474b7647e85a36e489469f83905c94785f2f278807532": { "Name": "api", "EndpointID": "e291176e7bd08040c2486d0409dab009a724fb7e3f0da3a5a9585cb23344a999", "MacAddress": "02:42:ac:13:00:03", "IPv4Address": "172.19.0.3/16", #<-これ!! "IPv6Address": "" } }, "Options": {}, "Labels": {} } ]コンテナ間通信をするときは、コンテナ名でエンドポイントを指定することができるから便利
# db:3306 の部分 <コンテナ名:ポート> MYSQL_CONNECTION=local:local@tcp(db:3306)/infra-challengeマウントについて
nginx
volumes: - ./web/env/nginx:/etc/nginx/conf.d - ./web/src:/var/www/html
- nginxには
default.confというものがあり、それを自分の設定に書き換えたいので、./web/env/nginxで書き換えてる/var/www/html以下がdockerコンテナ内のnginxフォルダ構成になる(する)ので、index.htmlがあるディレクトリを同期api
volumes: - ./api:/go/src/github.com/infra-challenge/api - /Users/username/go:/go
- とりあえずGo Pathが通っているところをdockerコンテナ側にマウントしてる。
- APIのファイル等も同期
db
volumes: - ./db/conf/my.cnf:/etc/mysql/conf.d/my.cnf - ./db/db-data:/var/lib/mysql
- MySQLのデータの格納先が
/var/lib/mysqlになるので、ホストの./db/db-dataに同期して、コンテナを除去してもデータが消えないようにする- 自分のDB設定、例えば、
character-setとかcollationとかをマウントして上書き# my.cnf [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci [mysql] default-character-set=utf8mb4いざ、Docker Composeで起動してみる!!
コマンド
$ cd /Users/username/src/github.com/infra-challege/docker-local $ docker network create --driver bridge local_infra_challenge_networks [~省略~] $ docker-compose build --no-cache [~省略~] $ docker-compose up -d --remove-orphans Creating db ... done Creating api ... done Creating nginx ... done $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8fxxxb102ed4 infra-challenge:nginx-20210312 "nginx -g 'daemon of…" 4 seconds ago Up 3 seconds 0.0.0.0:80->80/tcp nginx 8fxxx25640e6 golang:1.14.7-alpine3.12 "go run main.go --ho…" 6 seconds ago Up 4 seconds 0.0.0.0:8080->8080/tcp api e5xxxe97320e mysql:5.7 "docker-entrypoint.s…" 6 seconds ago Up 5 seconds (health: starting) 0.0.0.0:3306->3306/tcp, 33060/tcp db
- docker-composeファイルがある階層に移動
networks属性を指定したので、そのネットワークを作成する。- nginxはDockerfileをつかってimageを作成するので、ビルドする必要がある。
--no-cacheオプション:構築時にイメージのキャッシュを使わない。- docker-compose upで起動
-dオプション:バックグラウンド実行--remove-orphansオプション:Composeファイルで定義されていないサービス用のコンテナを削除Webページにアクセスできるか確認
コンテナ終了コマンド
$ docker-compose down Stopping nginx ... done Stopping api ... done Stopping db ... done Removing nginx ... done Removing api ... done Removing db ... done止まりました。
ハマったポイント
go run main.goした後に、起動はしていたがポート設定がミスっていて、APIにアクセスできなかった。
- 「なんかうまくいかないな〜〜汗」ってなったので、VSCodeから一旦APIサーバーを止めて再起動して、通信がうまくいったけど、それはコンテナ間通信ではなかった、、、
- コンテナ上ではなくローカルでAPIサーバーが立ち上がってしまい、通信できていると勘違いしてしまった。
index.htmlの画面表示するポートとAPIを司るポートそれぞれが別だったので、その調整にハマった。- nginxには、サーバー設定が必要だった。
nginx.confとdefault.confというファイルが存在しこれのIPアドレス、ポート等を設定しないといけないので、前もってnginxの知識をいれておかないと簡単には動かなかった# site.conf (default.confの上書き) server { listen 80; listen 8080; # APIで8080ポートも使ってる関係で、これも必要だった server_name localhost; root /var/www/html; location / { index index.html index.htm; root /var/www/html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/www/html/error; } }まとめ
docker-compose.ymlでコンテナを起動して、Dockerアプリケーションを作る感覚がつかめたので、楽しかったですわい!
コンテナのコントロールってすごい簡単で、サクサク動くから、せっかちな自分でも全然苦じゃない。
マスターしていきたいなああああ以上、ありがとうございました!
- 投稿日:2021-03-15T09:59:02+09:00
【Node.js】Express.jsからMySQLのデータ加工、ejsへの受け渡し
目的
現在、MySQLの店舗データを編集できるアプリケーションを作成しています。Node.jsのexpress.jsを使用し、viewにはejsを利用しています。この記事では、初学者の私がつまずいた、パラメータの受け渡しについて記述します。
ずばり「企業一覧画面から、企業に属した店舗の一覧を表示させる」処理についてです。
一連の処理は、こんな感じです
企業一覧画面(ejsで表示)
→ 店舗名のリンクをクリック
→ express.js(app.js)でデータを加工
→ 加工したデータをejsに渡す
→ ejsで表示させる参照
・Express.js(node.js)からMySQLへの接続とCRUD操作
・[Node.js][Express]リクエストからパラメータを取得する・POSTされたデータを取得する
・for 文と push メソッドを使って配列要素を複数生成環境と周辺構造
・local (mac Big Sur)
・AWS MySQL基本となるデータベースとの接続やCRUD処理の書き方等については、リンク先の記事で作成しました。そちらを参考にしてください!
★Express.js(node.js)からMySQLへの接続とCRUD操作テーブル構造
テーブルの相関関係はこのような形です。
account_masterが企業、shop_masterが企業が運営する店舗を示しています。
テーブルの関係性を簡単に表すと、
account_master.account_id = shop_master.shop_account_idです。
企業側のaccount_idは店舗側のshop_account_idと同じことを意味しています。画面イメージ
少し雑ですが、画面のイメージは以下の通りです。
・企業 一覧画面(account_master_index.ejs)
・店舗一覧画面(shop_index)
企業名リンクをクリックすると、店舗一覧画面に遷移します。ディレクトリ構造
※現在、未完成のため一部のみ記載
account_app/ ├── node_modules ├── views │ ├── account_master_index.ejs │ ├── edit.ejs │ └── shop_index.ejs ├── .env ├── .gitignore ├── app.js ├── package.json └── README.md前提
参考サイトでは、簡易なテーブルのデータで作成されていたため、企業の一覧や、店舗の一覧は簡単に表示まで出来ました。しかし、企業に基づく店舗など関係性がちょっと複雑な場合、どう表示させればよいかが全く分かりませんでした。
先に企業一覧について、次に店舗一覧について記述します。
企業一覧画面
app.js
企業一覧画面は参考サイトを元に、テーブル名、select文を変えるだけでOKでした。※記述外の設定は参考サイトを参照ください。
//app.jsの企業一覧に関する部分 app.get('/', (req, res) => { const sql = "select * from account_master where is_deleted = 0"; con.query(sql, function (err, result, fields) { if (err) throw err; res.render('account_master_index',{account_master : result}); }); });
app.getは express.js によるアプリケーションのルート( 今回は、localhost:3000 )へのGETメソッドに対応します。con.queryでデータベースから取得します。取得するデータはconst sqlで表記した select文(解約していない企業の情報すべて)です。例外処理を記述した後、
res.renderの第一引数にデータを表示させたいテンプレートを設定し、第二引数に .ejs に渡す名前を指定します。この命名により、ejs側ではresultではなくaccount_masterを使って取得したデータを利用できます。account_master_indx.ejs
一部抜粋した記述です。
//account_master_indx.ejsの中身 <% account_master.forEach(function (value) { %> <tr> <td><%= account_id %></td> ★ <td><a href="/shop_index/<%= value.account_id %>"><%= value.account_name %></a></td> <td><%= value.account_email.split('*') %></td> <td><%= value.mail_requested %></td> <td><%= value.is_deleted %></td> <td><%= value.updated_at %></td> <td><%= value.account_form_url %></td> <td><a href="/edit/<%= value.ue_account_id %>">up</a></td> </tr> <% }); %>参考サイトは
forEachを使っていたため、そのまま使っています。<% %>や、<%= %>で処理を書くのか、 HTML として表示させるかを書きます。★ここで4行目に注目
<a href="/shop_index/<%= value.account_id %>"><%= value.account_name %></a>で企業 id のパラメータをURLに指定しています。後にこれと同じURLにする処理を app.js 内に if 文で記述します。ここからが今回の記事の本題です
ポイントとしては以下のとおりです。
・企業に基づく店舗一覧をいきなり呼び出すのではなく、まず店舗の全データを出す
・配列を新たに作成する
・for文、if文で条件指定する
・加工した店舗一覧のデータをejs側で呼び出すapp.jsでデータを加工する
失敗した考え方
店舗一覧画面を出そうした際に、まず企業一覧画面と企業の編集画面を参考にしました。というのも、編集画面は企業のaccount_idを自然に渡せていたからです。そのため、店舗一覧画面も企業一覧と編集画面のようにaccount_idを受け渡せないかと考えました。そもそも、この↑部分の認識が間違っており、正しくはexpress.js側で処理したデータをejsで表示させる。つまり、ejsからgetメソッドで表示させる場合はejsかた値を受け渡すなどは行わないです。
成功した考え方
いきなり企業に紐づく店舗一覧を、expressで記述しそれをejs側で表示させるのではなく、まずapp.js内で先に店舗の全データを出します。そのデータを使って、必要な情報だけを載せた配列を新たに作成します。作成した配列をejs側で呼び出し、表示させます。店舗一覧画面
店舗一覧画面に関する記述は以下のとおりです。
app.js
//上部に宣言を記載 var shopDat; //app.jsの上部で、店舗の全データを取得 con.query('select * from shop_master where is_deleted = 0;', function (error, results, fields) { if (error) throw error; shopDat = results; //shopDatに代入 }); ~~(中略)~~ //企業ごとの店舗一覧を取得しshop_index.ejsで表示させるためのデータ加工 app.get('/shop_index/:shop_account_id', (req, res) => { let shops = []; //新たな配列を作成 for(let i = 0; i < shopDat.length; i++){ //※shopDat.lendgthは要変更 if (req.params.shop_account_id == shopDat[i].shop_account_id) { //URLパラメータの値を条件付け var target_shop = { //配列に入れるオブジェクトデータを定義 "shop_id": shopDat[i].shop_id, "shop_name_jp": shopDat[i].shop_name_jp, "shop_name_en": shopDat[i].shop_name_en, "shop_account_id": shopDat[i].shop_account_id, "is_deleted": shopDat[i].is_deleted, "updated_at": shopDat[i].updated_at }; shops.push(target_shop) //空の配列shopsに.pushで追加 } }; res.render('shop_index',{shop_data :shops}); //for文作成した配列をshop_dataと命名 });先に shopDat を宣言し、そこにMySQLから店舗の全データを代入します。店舗一覧画面を表示させる箇所に、 shops という新たな空の配列を作成します。更にi番目の shopDat のデータを保持させる target_shop というオブジェクトを作成します。
shops.push(target_shop)を shops という空の配列に、 for 文で作成されたオブジェクトを追加して、加工データを作成します。この時
req.params.shop_account_id == shopDat[i].shop_account_idではURLに渡すパラメータと企業に紐づく店舗の情報を一致させるために記述しています。※shop_account_idは企業のアカウント id と同じです。req.params はリクエストされたパスからパラメータを取得するに使う文字列です。
[Node.js][Express]リクエストからパラメータを取得する・POSTされたデータを取得するshop_index.ejs
//店舗一覧画面に関係する部分 <% for (let i = 0; i < shop_data.length; i++) { %> <tr> <td><%= shop_data[i].shop_id %></td> <td><%= shop_data[i].shop_name_jp %></td> <td><%= shop_data[i].shop_name_en %></td> <td><%= shop_data[i].shop_account_id %></td> <td><%= shop_data[i].updated_at %></td> <td><a href="/edit/<%= shop_data[i].shop_id %>">更新する</a></td> </tr> <% }; %>企業一覧画面では
forEachを使いましたが、こちらは for 文で表記しました。これで、企業に属する店舗の一覧を表示させることができました。小技とつまずいたポイント
・出力したいデータが正しいかどうかをHTML(.ejs)でみたい
→<%- JSON.stringify(shop_data) %>と記述することで見られます!・JavaScriptのデータは配列[ ]の中にオブジェクト{ }をもつことができる。
→ MySQLに格納されているデータはオブジェクトだったので、連想配列かと思ってしまいましたが、JavaScriptは [{ name:aaa, email:xxx@yyyy}, { name:bbb, email:yyy@xxxx}, { }....]とできるようです。(要勉強)・forとif文をに記述するのではなく、一つづつ書くこと
→ 処理を一気に書こうとして、ほしいデータをなかなか出すことが出来ませんでした。落ち着いて出力されたデータを見ながら、一つづつ解決するほうが結果早いですね。・app.jsにて、if文のshopDatに[ i ]をもたせること
→ iをつけることに、なかなか気がつけませんでした。まとめ
JavaScriptを勉強しはじめて3週間ほどですが、MySQLのデータを加工を実施しました!空の配列を作って、ほしいデータを作成をすることは初めての作業でしたが、なんとかうまく出来たので良かったです。途中で、配列なのか連想配列なのか迷ったりしたため中々答えにたどり着けませんでした。これが初学者の方のためになればと思います。
また、一部未完成・不十分な記述がありますので、ご教示いただけると幸いです!
以上
- 投稿日:2021-03-15T07:37:14+09:00
PHPのPDOでMySQLに接続する
環境
・macOS BigSur (11.2.2)
・PHP (7.4.12)
・MAMP (6.3)
・MySQL (5.7.32)前提条件
・MAMPのphpMyAdminでデータベース・テーブルを作成してあること
・データベースに接続するためのユーザーを作成してあることPDOとは
PHPからMySQLに接続するために必要なオブジェクトのこと
※PDOクラスのインスタンスを生成することにより、データベースサーバーとの接続が確率されるDB接続手順
//事前準備 $dsn = 'mysql:host=localhost;dbname=データベース名;charset=utf8'; $user = 'ユーザー名'; $pass = 'パスワード';変数に対象の「データベース情報」「DB接続するユーザー情報」を設定
//DB接続 $dbh = new PDO($dsn,$user,$pass);設定した変数を引数に入れて、PDOのオブジェクト生成
データを取得する
指定した列名のデータを全て取り出す例
※上記で生成したPDOオブジェクトを使用する
//SELECT文 $select_sql = 'SELECT * FROM テーブル名'; $stmt = $dbh->query($select_sql); foreach($stmt as $value){ echo $value['カラム名']; }データを登録する
入力フォームなどで入力された値を、プリペアドステートメントを使用してDBに登録する例
※プリペアドステートメントとは、SQL文を最初に用意しておいて、その後はクエリ内のパラメータの値だけを変更してクエリを実行できる機能のこと
//INSERT文 $insert_sql = 'INSERT INTO テーブル名(カラム名) VALUES(:name)'; $stmt = $dbh->prepare($insert_sql); $params = array(':name'=>$_POST['フォームに入力された値']); $stmt->execute($params);カラム名にデータ登録したいカラム名を指定する
VALUESを「:name」として抽象化し、後に$_POSTで値を設定する(※nameの部分は任意の文字で良い)
prepareメソッドでSQL文を実行する準備をする
executeメソッドでプリペアドステーメントを実行する※正式な値が入るまで一時的に場所を確保しておくために入れておく値のことをプレースホルダという
(今回だと「:name」の部分がプレースホルダ)
※プレースホルダに値を割り当てることをバインドというデータを削除する
指定された番号のIDを持つDBの値を削除する例
//DELETE文 $delete_sql = 'DELETE FROM テーブル名 WHERE idが入ってるカラム名 = :id'; $stmt = $dbh->prepare($delete_sql); $params = array('id'=>$_POST['指定された番号']); $stmt->execute($params);上記と同様、プリペアドステーメントを使用
データを変更する
指定された番号のIDを持つデータを対象に、指定されたカラム名の値を更新する例
//UPDATE文 $update_sql = 'UPDATE テーブル名 SET 更新するデータのカラム名 = :name WHERE 更新対象とするidが入ってるカラム名 = :id'; $stmt = $dbh->prepare($update_sql); $params = array('name'=>$_POST['更新するデータ'],'id'=>$_POST['指定された番号']); $stmt->execute($params);上記と同様、プリペアドステーメントを使用
try~catchを使ったエラーハンドリング(例外処理)
DB接続の成功/失敗を出力する例
try{ $dsn = 'mysql:host=localhost;dbname=データベース名;charset=utf8'; $user = 'ユーザー名'; $pass = 'パスワード'; $dbh = new PDO($dsn,$user,$pass); echo 'DB接続成功' }catch(PDOException $e) { echo 'DB接続エラー'; }(例外処理についての補足)
プログラム実行中に発生したエラーのことを例外という
例外が発生すると、プログラムが異常終了する
システム的に異常終了はさせたくないから、例外を検知して、例外が発生した場合に行う処理を用意する
この一連の処理のことを例外処理という
結果、エラーは発生しているが、プログラム自体は正常に動作したという状況を作ることができる
DBの接続が失敗したとき、プログラムが異常終了する
システム的に異常終了はさせたくないから、 DBの接続処理に対してエラーハンドリングする必要がある
- 投稿日:2021-03-15T07:37:14+09:00
PHP×MAMPでDB接続を行う
環境
・macOS BigSur (11.2.2)
・PHP (7.4.12)
・MAMP (6.3)
・MySQL (5.7.32)前提条件
・MAMPのphpMyAdminでデータベース・テーブルを作成してあること
・データベースに接続するためのユーザーを作成してあることPDOでDB接続
※PDOとは、MAMPでPHPからMySQLに接続するために必要なオブジェクトのこと
//事前準備 $dsn = 'mysql:host=localhost;dbname=データベース名;charset=utf8'; $user = 'ユーザー名'; $pass = 'パスワード';変数に対象の「データベース情報」「DB接続するユーザー情報」を設定
//DB接続 $dbh = new PDO($dsn,$user,$pass);設定した変数を引数に入れて、PDOのオブジェクト生成
データを取得する
生成したオブジェクトを使用してDBからデータを取り出す
※指定した列名のデータを全て取り出す例
//SELECT文 $select_sql = 'SELECT * FROM テーブル名'; $stmt = $dbh->query($select_sql); foreach($stmt as $value){ echo $value['列名']; }データを登録する
入力フォームなどで入力された値を、プリペアドステートメントを使用してDBに登録する例
※プリペアドステートメントとは、SQL文の値をいつでも変更できるように、変更する部分のみ変数のようにした命令文を作る仕組みのこと
//INSERT文 $insert_sql = 'INSERT INTO テーブル名(列名A) VALUES(:name)'; $stmt = $dbh->prepare($insert_sql); $params = array(':name'=>$_POST['フォームに入力された値']); $stmt->execute($params);列名Aにデータ登録したい列名を指定する
VALUESを「:name」として抽象化し、後に$_POSTで値を設定する(※nameの部分は任意の文字で良い)
prepareメソッドでSQL文を実行する準備をする
executeメソッドでプリペアドステーメントを実行する※「:name」と変化する値を確保することをプレースホルダという
※「:name」の部分に値を割り当てることをバインドというデータを削除する
指定された番号のIDを持つDBの値を削除する例
//DELETE文 $delete_sql = 'DELETE FROM テーブル名 WHERE idが入ってる列名 = :id'; $stmt = $dbh->prepare($delete_sql); $params = array('id'=>$_POST['指定された番号']); $stmt->execute($params);上記と同様、プリペアドステーメントを使用
データを変更する
指定された番号のIDを持つデータを対象に、指定された列名の値を更新する
//UPDATE文 $update_sql = 'UPDATE テーブル名 SET 更新するデータの列名 = :name WHERE 更新対象とするidが入ってる列名 = :id'; $stmt = $dbh->prepare($update_sql); $params = array('name'=>$_POST['更新するデータ'],'id'=>$_POST['指定された番号']); $stmt->execute($params);上記と同様、プリペアドステーメントを使用
try~catchを使ったエラーハンドリング(例外処理)
DB接続の成功/失敗を出力する例
※例外処理とは、エラーが発生した際の処理を設定しておくこと
(try~catchの間でエラーが発生した時、catchの中の処理が行われる)try{ $dsn = 'mysql:host=localhost;dbname=データベース名;charset=utf8'; $user = 'ユーザー名'; $pass = 'パスワード'; $dbh = new PDO($dsn,$user,$pass); echo 'DB接続成功' }catch(PDOException $e) { echo 'DB接続エラー'; }
- 投稿日:2021-03-15T07:37:14+09:00
PHP×MAMPでDB操作する
環境
・macOS BigSur (11.2.2)
・PHP (7.4.12)
・MAMP (6.3)
・MySQL (5.7.32)前提条件
・MAMPのphpMyAdminでデータベース・テーブルを作成してあること
・データベースに接続するためのユーザーを作成してあることPDOでDB接続
※PDOとは、MAMPでPHPからMySQLに接続するために必要なオブジェクトのこと
//事前準備 $dsn = 'mysql:host=localhost;dbname=データベース名;charset=utf8'; $user = 'ユーザー名'; $pass = 'パスワード';変数に対象の「データベース情報」「DB接続するユーザー情報」を設定
//DB接続 $dbh = new PDO($dsn,$user,$pass);設定した変数を引数に入れて、PDOのオブジェクト生成
データを取得する
指定した列名のデータを全て取り出す例
※上記で生成したオブジェクトを使用する//SELECT文 $select_sql = 'SELECT * FROM テーブル名'; $stmt = $dbh->query($select_sql); foreach($stmt as $value){ echo $value['列名']; }データを登録する
入力フォームなどで入力された値を、プリペアドステートメントを使用してDBに登録する例
※プリペアドステートメントとは、SQL文の値をいつでも変更できるように、変更する部分のみ変数のようにした命令文を作る仕組みのこと
//INSERT文 $insert_sql = 'INSERT INTO テーブル名(列名A) VALUES(:name)'; $stmt = $dbh->prepare($insert_sql); $params = array(':name'=>$_POST['フォームに入力された値']); $stmt->execute($params);列名Aにデータ登録したい列名を指定する
VALUESを「:name」として抽象化し、後に$_POSTで値を設定する(※nameの部分は任意の文字で良い)
prepareメソッドでSQL文を実行する準備をする
executeメソッドでプリペアドステーメントを実行する※「:name」と変化する値を確保することをプレースホルダという
※「:name」の部分に値を割り当てることをバインドというデータを削除する
指定された番号のIDを持つDBの値を削除する例
//DELETE文 $delete_sql = 'DELETE FROM テーブル名 WHERE idが入ってる列名 = :id'; $stmt = $dbh->prepare($delete_sql); $params = array('id'=>$_POST['指定された番号']); $stmt->execute($params);上記と同様、プリペアドステーメントを使用
データを変更する
指定された番号のIDを持つデータを対象に、指定された列名の値を更新する
//UPDATE文 $update_sql = 'UPDATE テーブル名 SET 更新するデータの列名 = :name WHERE 更新対象とするidが入ってる列名 = :id'; $stmt = $dbh->prepare($update_sql); $params = array('name'=>$_POST['更新するデータ'],'id'=>$_POST['指定された番号']); $stmt->execute($params);上記と同様、プリペアドステーメントを使用
try~catchを使ったエラーハンドリング(例外処理)
DB接続の成功/失敗を出力する例
※例外処理とは、エラーが発生した際の処理を設定しておくこと
(try~catchの間でエラーが発生した時、catchの中の処理が行われる)try{ $dsn = 'mysql:host=localhost;dbname=データベース名;charset=utf8'; $user = 'ユーザー名'; $pass = 'パスワード'; $dbh = new PDO($dsn,$user,$pass); echo 'DB接続成功' }catch(PDOException $e) { echo 'DB接続エラー'; }
- 投稿日:2021-03-15T02:09:55+09:00
Laravel8 ReactHooks & MySQL Create処理
やりたいこと
フロントはReactHooksを使用して、LaravelはAPIサーバーでバックエンド処理をおこなう。
バックエンド処理はaxiosを使用。完成図
開発環境
- Laravel 8.0
- php 7.3
- MySQL 8
State
- const [ name, setName ] = useState(null);
入力値name(名前)のState
- const [ email, setEmail ] = useState(null);
入力値email(メールアドレス)のState
- const [ city, setCity ] = useState(null);
入力値city(住所)のState
- const [ address, setAddress ] = useState(null);
入力値address(市区町村)のState
- const [ phone, setPhone ] = useState(null);
入力値phone(電話番号)のState
入力フォーム
map処理はkeyを明示する必要があるので注意
keyの明示についてはVirtualDOMのdiffから実際のDOMに反映させるときに最小限の変更にするために使われる。
formimport React, { useEffect, useState } from 'react'; import employeeServices from "../services/Employee" function Form(){ const [ name, setName ] = useState(null); const [ email, setEmail ] = useState(null); const [ city, setCity ] = useState(null); const [ address, setAddress ] = useState(null); const [ phone, setPhone ] = useState(null); const [ rol, setRol ] = useState(null); const [ listRol, setListRol ] = useState([]); useEffect(() => { async function fetchDataRol() { // load data from API const res = await employeeServices.list(); setListRol(res.data) } fetchDataRol(); },[]); const saveEmployee = async () => { const data = { name, email, city, address, phone, rol } const res = await employeeServices.save(data); if (res.success) { alert(res.message) } else { alert(res.message) } } return( <div> <div className="row"> <div className="col-md-6 mb-3"> <label htmlFor="firstName">Name employee </label> <input type="text" className="form-control" placeholder="Name" onChange={(event)=>setName(event.target.value)} /> </div> </div> <div className="row"> <div className="col-md-6 mb-3"> <label htmlFor="email">Email</label> <input type="email" className="form-control" placeholder="you@example.com" onChange={(event)=>setEmail(event.target.value)} /> </div> </div> <div className="row"> <div className="col-md-6 mb-3"> <label htmlFor="phone">City </label> <select id="inputState" className="form-control" onChange={(event)=> setCity(event.target.value)}> <option selected>Choose...</option> <option value="London">London</option> <option value="Madrid">Madrid</option> <option value="New York">New York</option> </select> </div> </div> <div className="row"> <div className="col-md-6 mb-3"> <label htmlFor="address">Address</label> <input type="text" className="form-control" placeholder="1234 Main St" onChange={(event)=>setAddress(event.target.value)} /> </div> </div> <div className="row"> <div className="col-md-6 mb-3"> <label htmlFor="phone">Phone </label> <input type="text" className="form-control" placeholder="123467890" onChange={(event)=>setPhone(event.target.value)} /> </div> </div> <div className="row"> <div className="col-md-6 mb-3"> <label htmlFor="phone">Rol </label> <select id="inputState" className="form-control" onChange={(event)=> setRol(event.target.value)}> <option selected>Choose...</option> { listRol.map((item)=>{ return( <option key={item.rol_id} value={item.rol_id}>{item.rol_name}</option> ) }) } </select> </div> </div> <div className="row"> <div className="col-md-6 mb-3"> <button className="btn btn-primary btn-block" type="submit" onClick={()=>saveEmployee()}>Save</button> </div> </div> </div> ) } export default Form;saveEmployee押化後、axiosのemployee.saveが呼び出されてlocalhost:8000/api/employee/createにPOSTする。
axios通信
axiosconst baseUrl = "http://localhost:8000/api/employee" import axios from "axios"; const employee = {}; employee.save = async (data) => { const urlSave= baseUrl+"/create" const res = await axios.post(urlSave,data) .then(response=> {return response.data }) .catch(error=>{ return error; }) return res; } export default employeeapi.php<?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); Route::post('/employee/create', 'App\Http\Controllers\API\EmployeeController@create');Laravel側のControllerが呼び出され、実際に処理をするcreateアクションを呼び出します。
保存処理の最後にはresponseを返してあげる。今回は動作確認のため、失敗時にエラーメッセージのみを返しています。
コントローラー
EmployeeController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Employee; class EmployeeController extends Controller { public function index(){ return view("employee"); } public function create(Request $request){ try { $insert['name_lastname'] = $request['name']; $insert['email'] = $request['email']; $insert['city'] = $request['city']; $insert['direction'] = $request['address']; $insert['phone'] = $request['phone']; Employee::insert($insert); $response['message'] = "Save succesful"; $response['succes'] = true; } catch (\Exception $e) { $response['message'] = $e->getMessage(); $response['error'] = true; } return $response; } }createアクションはこのような流れで実装できます
指摘などがございましたらコメントで教えていただけると幸いです^_^
おわり
次はレコードをmapで繰り返し表示する方法など記事にしていこうと思います〜!
- 投稿日:2021-03-15T02:09:55+09:00
Laravel8+ReactHooks+MySQL Create処理
やりたいこと
フロントはReactHooksを使用して、LaravelはAPIサーバーでバックエンド処理をおこなう。
バックエンド処理はaxiosを使用。開発環境
- Laravel 8.0
- php 7.3
- MySQL 8
ModelはItem カラムはtext/commentと仮定しています。
State
- const [ text, setText ] = useState("");
入力値textのState
- const [ comment, setComment] = useState("");
入力値commentのState
入力フォーム
入力値をSetStatで更新。submitでsaveEmployeeを呼び出す。
formreturn ( <form className="add-form"> <div className="add-text"> <label>内容</label> <input type="text" value={text} onChange={(event)=>setText(event.target.value)}/> </div> <div className="add-comment"> <label>コメント</label> <input type="number" value={amount} onChange={(event)=>setComment(event.target.value)}}/> </div> <div className="form-row"> <color="primary" aria-label="add" type="submit" onClick={()=>saveEmployee()}>追加</div> </form> )呼び出されたsaveEmployeeはdataに定義された値を保持してemployeeServices.saveにアクセス。
この際、dataに定義されているtextとcommentはuseStateを参照しています。axiosconst saveEmployee = async () => { const data = { text, comment, }; const res = await employeeServices.save(data); setItemlist(res.data) }saveEmployeeから渡された値(data)を引数に、LaravelのAPIサーバーにPOSTする。
employeelet url = location.href; const baseUrl = url + 'api/employee'; const employee = {}; employee.save = async (data) => { const urlSave = baseUrl+"/save" const res = await axios.post(urlSave,data) .then(response=>{ return response.data; }) .catch(error=>{ return error; }) return res; }Laravel側のapi.phpが呼び出され、実際に処理をするcreateアクションを呼び出します。
api.phpuse App\Http\Controllers\EmployeeController; Route::match(['get', 'post'],'/employee/itemcreate', [EmployeeController::class, 'itemcreate']);itemcreateメソッド内で保存処理を行い、responseに$itemsを渡してreturnしてあげる。
ItemController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Item; class EmployeeController extends Controller { public function itemcreate(Request $request){ try { $items = new Item(); $items->text = $request->text; $items->amount = $request->comment; $items->save(); $response['data'] = $items; $response['message'] = '成功'; $response['success'] = true; } catch (\Exception $e) { $response['message'] = $e->getMessage(); $response['success'] = false; } return $response; } }createアクションはこのような流れで実装できます
指摘などがございましたらコメントで教えていただけると幸いです^_^
次はレコードをmapで繰り返し表示する方法など記事にしていこうと思います〜!
- 投稿日:2021-03-15T01:29:50+09:00
PHPをherokuでデプロイする!(3)データベースのインポート
はじめに
PHPをherokuでデプロイする方法、今回はローカル環境で作ったデータベースの引継ぎとしてインポートする方法を書いていきます!
データベースをインポートするための準備
まずはローカル環境で作ったデータベースを引き継ぐ形でデータベースを作成していきます。前回でcleardbのアドオンを用いてMySQLが利用できるようになっていると思います。PHPではPHPMyAdminでデータベースの作成や編集、データ内容の確認ができますが、あくまでこれはローカル環境のみとなっています。herokuでデータベースをインポートするために便利なのが、「MySQL Workbench」です。
MySQL Workbenchのインストール
MySQL WorkbenchはMySQLが公式に提供しているアプリで、GUIで操作しやすいのが特徴です。ダウンロードはこちらです。
https://www.mysql.com/jp/products/workbench/
「ダウンロードはこちら」ボタンを押し自分のOSを選んでダウンロードし、インストールを進めましょう。ダウンロードする前にユーザー登録を求められますが、「No thanks, just start my download.」が下に表示されているのでそれを押すとユーザー登録なしにダウンロードできます。なお、最新バージョンだとインストールしたあとmacでは動作しないことがあったようです。その場合はArchivesボタンを押して、8.0.19をダウンロード、インストールすると確実に動作します。インストール後表示にしたがってアイコンをドラッグするとmacであればApplicationsフォルダーにコピーされます。
MySQL Workbenchの設定
起動させると
こんな画面になります。MySQLConnectionsの隣にある+のボタンを押すとデータベースの作成ができます。
ここで入力していくのはherokuのデータベースの情報です。(1)の中でも紹介した、herokuのデータベース情報を元に入力しましょう。herokuのデータベース情報はターミナルでデプロイしたいアプリにアクセスして以下を入力してください。heroku config | grep CLEARDB_DATABASE_URLここで表示されるのは、CLEARDB_DATABASE_URL: mysql://ユーザー名:パスワード@ホスト名/データベース名?reconnect=trueとなります。
これに基づいて先ほどの画像の内容を入れていきましょう。まず、「connection name」ですがこれはこのデータベースを管理する上での名前なので、好きに名付けて構いません。「conecction Method」はそのままの設定で進めましょう。
Hostnameは、先ほどherokuconfigで確認したホスト名を、Usernameも確認したユーザー名を入れましょう。Portはそのままで構いません。
そしてパスワードは「Store in Key chain」ボタンをクリックすると入力できます。入力内容が確認できないので間違いないように気をつけてください。
「Default Schema」にはデータベース名を入れましょう。全て入れたらTest conecctionボタンを押してみましょう。そうすると問題なければ「Successfully made the MySQL connection」と表示されます。もし問題がある場合は入力内容に間違いがないかみていきましょう。これで接続できていれば、データベースをインポートする準備ができています!
データベースのエクスポート
次に、インポートするためにPHPMyAdminからデータをエクスポートします。
PHPMyAdminにアクセスし、エクスポートボタンを押して、エクスポート方法で詳細を押すと、データベースの選択ができるので、エクスポートしたいデータベースを選びましょう。
出力は必ず「出力をファイルに保存する」を選びましょう。これでないとインポートできません。その他はそのままで問題ないので、実行を押すとファイルがダウンロードされます。データベースのインポート
それでは、いよいよインポートの作業に入ります!MySQL workbenchを再び開くと、先ほど追加したデータベースが表示されていると思います。ボタンを押すと少し接続の待ち時間があり、その後画面が開きます。
このような画面になったら、Data Import/Restoreを開きましょう。
そうするとこんな感じの画面になるので、「Import from Self-Contained File」を選択し、先ほどエクスポートしたファイルを選びましょう。また「Default Target Schema」ではherokuのデータベース名を選択しましょう。
そして「Import progress」を選択し、「Start Import」を押せばインポート完了です!もしうまくいかない時は・・・
ちなみに、もしインポートできない場合は、データベース名をコメントアウトし、テーブル名をのみを残すような形で編集するとうまくいくことがあります!ちなみにエクスポートしたデータベースは右クリックで「このアプリケーションから開く」を選択し、VScodeなどプログラミング用のアプリケーションを開けば内容を見ることができます。
次回はいよいよ最終章として、デプロイに必要なcomposerのインストール、そしてデプロイの方法について書いていきます!














