- 投稿日:2020-11-27T23:01:50+09:00
Terraformを用いてEC2およびRDSを立ち上げ、Laravelの環境を構築する
構成
Terraformで
ap-northeast-1
リージョンにVPC、1つのパブリックサブネットおよび2つのプライベートサブネットを作成した。さらにパブリックサブネット内にEC2インスタンスを立て、プライベートサブネットにはRDS for MySQLを設置した。ここまでをTerraformで行った。
EC2インスタンス内にはSSH接続でログインし、Laravelの環境を構築した。
>>TerraformでEC2、RDSを構築する
IAMユーザーを作成する
AWSコンソールからIAMダッシュボードへ移動する。
ユーザーを追加を選択する。
ユーザー名を入力し、プログラムによるアクセスを選択する。
既存のポリシーを直接アタッチを選択し、AdministratorAccess
を選択する。
内容を確認し、ユーザーの作成を選択する。
アクセスキーID
とシークレットアクセスキー
を控える。
~/.aws/credentials
にaws_access_key_id
およびaws_secret_access_key
を記述する。% mkdir ~/.aws % (echo "[default]"; echo "aws_access_key_id = xxxxxxxxxxxxxxxxxxxx"; echo "aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") >> ~/.aws/credentialsこの状態になる。
~/.aws/credentials[default] aws_access_key_id = xxxxxxxxxxxxxxxxxxxx aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTerraformのファイルを用意する
ファイルの構成
sample ├── aws_db_instance.tf ├── aws_db_parameter_group.tf ├── aws_db_subnet_group.tf ├── aws_instance.tf ├── aws_internet_gateway.tf ├── aws_route_table.tf ├── aws_route_table_association.tf ├── aws_security_group.tf ├── aws_subnet.tf ├── aws_vpc.tf ├── main.tf ├── secret.tfvars └── terraform.tfvarsリソースごとにファイルを分けた。
今回は13ファイル用意した。ファイルの内容
main.tfterraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 3.0" } } } # Configure the AWS Provider provider "aws" { region = "ap-northeast-1" } # variables variable "app" { type = string } variable "region" { type = string } variable "vpc_cidr_block" { type = string } variable "subnet_cidr_block1" { type = string } variable "subnet_cidr_block10" { type = string } variable "subnet_cidr_block11" { type = string } variable "ssh_cidr_blocks" { type = list description = "allowed SSH connection" default = ["0.0.0.0/0"] } variable "db_name" { type = string } variable "db_username" { type = string } variable "db_password" { type = string }aws_vpc.tf# Create a VPC resource "aws_vpc" "main" { cidr_block = var.vpc_cidr_block tags = { Name = var.app } }aws_subnet.tf# Create a subnet resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = var.subnet_cidr_block1 availability_zone = "${var.region}a" tags = { Name = "${var.app}-public-subnet" } } resource "aws_subnet" "private1" { vpc_id = aws_vpc.main.id cidr_block = var.subnet_cidr_block10 availability_zone = "${var.region}a" tags = { Name = "${var.app}-private1-subnet" } } resource "aws_subnet" "private2" { vpc_id = aws_vpc.main.id cidr_block = var.subnet_cidr_block11 availability_zone = "${var.region}c" tags = { Name = "${var.app}-private2-subnet" } }aws_internet_gateway.tf# Provides a resource to create a VPC Internet Gateway. resource "aws_internet_gateway" "gw" { vpc_id = aws_vpc.main.id tags = { Name = var.app } }aws_route_table.tf# Provides a resource to create a VPC routing table. resource "aws_route_table" "public_route" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.gw.id } tags = { Name = "${var.app}-public-route" } }aws_route_table_association.tf# Provides a resource to create an association between a route table and a subnet or a route table and an internet gateway. resource "aws_route_table_association" "public_route" { subnet_id = aws_subnet.public.id route_table_id = aws_route_table.public_route.id }aws_security_group.tf# Provides a security group resource. resource "aws_security_group" "instance" { name = var.app description = "secutity group for instanse of ${var.app}" vpc_id = aws_vpc.main.id ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = var.ssh_cidr_blocks } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.app}-instance-security-group" } } resource "aws_security_group" "db_instance" { name = "${var.app}-db" description = "security group for db instance of ${var.app}" vpc_id = aws_vpc.main.id ingress { from_port = 3306 to_port = 3306 protocol = "tcp" security_groups = [aws_security_group.instance.id] } egress { from_port = 0 to_port = 0 protocol = "-1" } tags = { Name = "${var.app}-db-instance-security-group" } }aws_instance.tf# Provides an EC2 instance resource. resource "aws_instance" "web" { ami = data.aws_ssm_parameter.amzn2_ami.value instance_type = "t2.micro" associate_public_ip_address = true subnet_id = aws_subnet.public.id vpc_security_group_ids = [aws_security_group.instance.id] key_name = "sample" tags = { Name = var.app } } # Provides an SSM Parameter data source. data "aws_ssm_parameter" "amzn2_ami" { name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" }
aws_ssm_parameter
のData SourceでAmazon Linux 2のAMI IDを取得している。
この記事が参考になった。
本記事内ではkey_name
はsample
としている。
後ほどsample
という名前のキーペアを作成する。aws_db_subnet_group.tf# Provides an RDS DB subnet group resource. resource "aws_db_subnet_group" "default" { name = "${var.app}-db-subnet-group" description = "${var.app}-db-subnet-group" subnet_ids = [ aws_subnet.private1.id, aws_subnet.private2.id ] tags = { Name = "${var.app}-db-subnet-group" } }aws_db_parameter_group.tf# Provides an RDS DB parameter group resource. resource "aws_db_parameter_group" "default" { name_prefix = var.app family = "mysql8.0" description = "${var.app} parameter group for mysql8.0" parameter { name = "time_zone" value = "Asia/Tokyo" } tags = { Name = var.app } }aws_db_instance.tf# Provides an RDS instance resource. resource "aws_db_instance" "default" { allocated_storage = 20 storage_type = "gp2" engine = "mysql" engine_version = "8.0" instance_class = "db.t2.micro" db_subnet_group_name = aws_db_subnet_group.default.id vpc_security_group_ids = [ aws_security_group.db_instance.id ] parameter_group_name = aws_db_parameter_group.default.id name = var.db_name username = var.db_username password = var.db_password skip_final_snapshot = true tags = { Name = "${var.app}-rds" } }変数ファイル
terraform.tfvarsapp = "sample" region = "ap-northeast-1" vpc_cidr_block = "10.1.0.0/16" subnet_cidr_block1 = "10.1.1.0/24" subnet_cidr_block10 = "10.1.10.0/24" subnet_cidr_block11 = "10.1.11.0/24"
terraform.tfvars
ファイルを作って変数に値を渡す。secret.tfvarsssh_cidr_blocks = ["x.x.x.x/32"] // ← allowed SSH connection db_name = "sample" db_username = "xxxxxx" db_password = "xxxxxx"
secret.tfvars
などのようにterraform.tfvars
以外の名前を使う場合は-var-file
を使う。
クレデンシャル情報を書く場合は、同ファイルをバージョン管理の対象外としておく。
今回はsecret.tfvars
にクレデンシャル情報を書いた。
db_name
、db_username
およびdb_password
の値は Laravelの環境変数を設定する際に使用する 。terraform を実行する
% terraform initterraformの作業ディレクトリを初期化している。
% terraform plan -var-file="secret.tfvars"
terraform plan
差分を確認する。
-var-file="secret.tfvars"
で変数を渡している。
terraform.tfvars
に書いた変数は-var-file
無しで渡せる。% terraform apply -var-file="secret.tfvars"
terraform apply
でAWSにサービスを構築する。
これでAWS上で下図の状態になる。
あとはSSH接続し、EC2インスタンス内に必要なものをインストールしていく。もし構築したものをすべて壊すときは
terraform destroy -var-file="secret.tfvars"
を実行する。SHH接続用のキーペアを作成する
AWSコンソールからEC2ダッシュボードへ移動する。
キーペア を選択する。
キーペアを作成 を選択する。
キーペアを作成する。
キーペアが作成される。
キーペアを作成すると秘密鍵がダウンロードされる。
秘密鍵は~/.ssh
へ移動させて、権限を変更してユーザーのみ読み込み可能にしておく。% mv ~/Downloads/sample.pem ~/.ssh/ % chmod 400 ~/.ssh/sample.pemAmazon EC2 キーペアと Linux インスタンス | オプション 1: Amazon EC2 を使用してキーペアを作成する - Amazon Elastic Compute Cloud
EC2インスタンス内にLaravelの環境を構築する
AWSコンソールからEC2ダッシュボードへ移動する。
インスタンスを選択し、パブリックIPアドレスをコピーする。SSHクライアントでEC2インスタンスに接続する
% ssh -i ~/.ssh/sample.pem ec2-user@x.x.x.xSSH接続でEC2にログインする。
__| __|_ ) _| ( / Amazon Linux 2 AMI ___|\___|___| https://aws.amazon.com/amazon-linux-2/ No packages needed for security; 3 packages available Run "sudo yum update" to apply all updates.nginxとphpをインストールする
$ amazon-linux-extras利用可能なパッケージを確認する。
今回はnginx1
とphp7.4
を利用する。$ sudo amazon-linux-extras install -y php7.4 nginx1インストールする。
必要なパッケージも同時にインストールされる。$ sudo systemctl start nginx php-fpmnginxおよびphp-fpmを起動させる。
nginxを起動させてから http://x.x.x.x にアクセスすると
このように表示される。$ systemctl status nginx php-fpm
で起動出来ているかどうかの確認ができる。
必要なパッケージのインストール
Laravel 8.x インストールによると、
以下の要件を満たす必要があります。
PHP >= 7.3
BCMath PHP拡張
Ctype PHP拡張
Fileinfo PHP拡張
JSON PHP拡張
Mbstring PHP拡張
OpenSSL PHP拡張
PDO PHP拡張
Tokenizer PHP拡張
XML PHP拡張従って、必要なパッケージをインストールする。
$ sudo yum install -y php-bcmath php-ctype php-fileinfo php-json php-mbstring php-openssl php-pdo php-tokenizer php-xmlComposerをインストールする
$ curl -sS https://getcomposer.org/installer | phpComposerをインストールする。
$ sudo mv composer.phar /usr/local/bin/composer移動させてパスを通す。
nginxとphpの設定
nginxの設定
$ sudo vi /etc/nginx/conf.d/default.conf
/etc/nginx/conf.d/default.conf
を作成し、以下の内容に変更する。/etc/nginx/conf.d/default.confserver { listen 80; server_name _; root /var/www/sample/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; index index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/php-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; } }内容はデプロイ 8.x Laravelを参考にしている。
php-fpmの設定
$ sudo vi /etc/php-fpm.d/www.conf
/etc/php-fpm.d/www.conf
の以下の値を変更する。/etc/php-fpm.d/www.confuser = nginx group = nginx listen = /run/php-fpm/php-fpm.sock listen.owner = nginx listen.group = nginx listen.mode = 0660$ sudo systemctl restart nginx php-fpm再起動する。
$ sudo mkdir /var/www && sudo chmod 777 /var/www
/var/www
ディレクトリを作成し、全権限を与える。Laravelをインストールする
$ composer create-project --prefer-dist laravel/laravel /var/www/sampleインストールする。
$ sudo chmod -R 777 /var/www/sample/{storage,bootstrap/cache/}
/var/www/sample/storage/
および/var/www/sample/bootstrap/cache/
の権限を変更し、全権限を与える。データベースの環境変数を設定する
$ vi /var/www/sample/.env
/var/www/sample/.envDB_CONNECTION=mysql DB_HOST=エンドポイント DB_PORT=3306 DB_DATABASE=データベース名 DB_USERNAME=ユーザー名 DB_PASSWORD=パスワード
エンドポイント
はRDSダッシュボードから確認する。①
②
③
④
データベース名
、ユーザー名
、およびパスワード
はterraformに変数で渡した値。マイグレーション
$ cd /var/www/laravel $ php artisan migrate:fresh --seed成功すれば、LaravelとMySQLの接続も問題ない。
EC2インスタンス内にも必要なものが揃い、目的の構成の状態になった。npmを使うためには
$ curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash - && sudo yum install -y nodejsインストールする。
gitが必要な場合
$ sudo yum install -y gitインストールする。
mysqlにログインする場合
$ sudo yum install -y mysqlインストールする。
$ mysql -h エンドポイント -u ユーザー名 -pログインする。
あとがき
もともと ECS、ECRを使って下図のような構成にしようと思っていました。
しかし、おそらくタスク定義に問題があり、ローカルでは問題無いもののAWS上ではnginxとphp-fpmの接続がうまくいきませんでした。
どなたかわかる方が居りましたら教えてください。参考
- 投稿日:2020-11-27T22:56:40+09:00
LaravelでSQL実行計画を取得(explain)する
何がしたいか
Laravel8 でexplainメソッドが追加されました。
User::where('id',1)->explain()->dump(); DB::table('id',1)->where('id',1)->explain()->dump();https://www.amitmerchant.com/using-mysql-explain-for-queries-in-laravel-8/
Laravel8未満でも使いたいなーと思いやってみることに
環境
下記環境で動作を確認してます。
Laravel 7.xやり方
macroで拡張できるので、メソッドを追加して、マージされたプルリクの内容を
AppServiceProvider
にペタリAppServiceProvideruse Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Query\Builder as QueryBuilder; public function boot(): void { QueryBuilder::macro('explain', function () { $sql = $this->toSql(); $bindings = $this->getBindings(); $explanation = $this->getConnection()->select('EXPLAIN ' . $sql, $bindings); return collect($explanation); }); EloquentBuilder::macro('explain', function () { $sql = $this->toSql(); $bindings = $this->getBindings(); $explanation = $this->getConnection()->select('EXPLAIN ' . $sql, $bindings); return collect($explanation); }); }これでとれるようになった。
User::where('id',1)->explain()->dump(); DB::table('id',1)->where('id',1)->explain()->dump();ログに出す
そもそも、ログ出すのに使う必要ないので、explainメソッドいらない子かもしれない...
サービスプロバイダのboot
に下記を追加(参考)AppServiceProvider\DB::listen(function ($query) { if (!preg_match("/^EXPLAIN/", $query->sql)) { $explain = \DB::select("EXPLAIN {$query->sql}", $query->bindings); \Log::info($explain); } });
- 投稿日:2020-11-27T20:59:12+09:00
Laravelのscss(background)で画像を読み込ませたい
SCSSに画像を読み込ませたい
cssで画像を読み込ませたいbackground: url("../../../public/img/lp/icn-check.png") no-repeat 5px 40%;たったこれだけに少し苦戦をしたので、アウトプットしておきたいと思います。
webpack.mix.jsを編集
まずwebpack.mix.jsを編集します。
webpack.mix.jsmix.js("resources/js/app.js", "public/js") .sass("resources/sass/app.scss", "public/css") .options({ processCssUrls: false });ポイントをそれぞれみます。
.sass("resources/sass/app.scss", "public/css")
"public/css"
をつけて、cssの保存先のフォルダを指定します。
詳細
そして、oputinsを付与します。optionsを付与.options({ processCssUrls: false });これをつけない場合、通常下記のようになる。
css.example { background: url(/images/example.png?d41d8cd98f00b204e9800998ecf8427e); }こうなると指定した画像名と異なるため、画像を読み込めない。
なので、processCssUrls: false
にする。ファイルを指定する。
さあ、準備ができました。
あとは画像を指定するだけです。
ここから重要です。scssファイルの階層は、
アプリ名/resources/sass/lp/top.scss
画像ファイルの階層は、下記に入れたとします(人によります)。
アプリ名/public/img/lp/icn-check.png
画像は
resourcs
フォルダの兄弟ディレクトリである、public
フォルダに入れています。
そのため、上の階層に戻す必要があります。なので、階層を戻していきます。
アプリ名/resources/sass/lp/top.scss
なので、3階層戻ります。background: url("../../../");これでresourcesの親階層になります。
resources
の兄弟ディレクトリである、public
フォルダを指定したいので、background: url("../../../public/");あとは画像を入れているフォルダを指定してやればいいので、
background: url("../../../public/img/lp/icn-check.png") no-repeat 5px 40%;となります。
はじめは
public/css
を読み込んでいると思っていましたが、そうではなくsass
ファルダから画像フォルダまでのパスを記載してやる必要がありました。
- 投稿日:2020-11-27T18:52:47+09:00
LaravelAdminのシャーディング対応
本記事は、サムザップ Advent Calendar 2020 #2 の12/6の記事です。
LaravelAdminは、いわゆる管理画面のフレームワークです。(公式: https://laravel-admin.org/docs/en/)
シンプルながらデザインが整っていて、いちから作るよりはよい機能がそろっています。また、CRUDで作られていて、定型的な表示や操作ならモデル指定ぐらいで作れたりします。
一方で、ゲーム開発のサーバでは負荷分散のためにシャーディングをしていることが多く、データベースとテーブルを複数組み合わせてひとつのデータを示していることがよくあります。
LaravelAdminでは、そのままこれらのデータを扱うことができません。
ここではそんなLaravelAdminでシャーディングしたデータを扱うためのいくつかの方法を述べてみます。何が問題?
LaravelAdminではGrid、Formなどいくつかの表示や操作の仕組みを提供していて、それらのクラスに対してモデルを渡したり表示内容などを記述するといった作り方をしています。
渡すモデルは、あらかじめ生成したモデルを渡すことができるので、シャーディングされたDBやテーブルをシャーディングキーで特定することが事前にできれば、そのシャーディング先を示すモデルを扱うことはできます。
ただシャーディングされたデータをそのままGridに渡すと、全件表示されてしまいます。たとえばユーザーIDをシャーディングキーにしているものなら、ユーザーIDを指定しても、ほかのユーザーのデータも表示されてしまいます。
ほかにも条件による絞り込み機能など、サブ的な仕組みには、シャーディングキーを呼び出し元と揃えたい場合があり、シャーディングキーをどうやって渡すかがポイントになりがちです。シャーディングに対応したgridの作成
Gridは便利な機能ですが、そのままでは扱えないので、LaravelAdminのコードを改造することにしました。
考え方として、シャーディングキーをあらかじめフィルターのように使えば、Gridの表示の際、シャーディングキーに合わせた表示がされるはずです。この仕組みを「デフォルトのフィルター」としてGridに用意してやればいいわけです。
vendor/encore/laravel-admin/src/Grid.phpに以下のコードを追加します。Grid.php/** * default filter key * @var string */ protected $defaultFilterKey; /** * default filter value * @var mixed */ protected $defaultFilterValue; /** * Add default filter. * * @return void */ public function addDefaultFilter($key, $value) { $this->defaultFilterKey = $key; $this->defaultFilterValue = $value; }同じGrid.phpのapplyQuery()では、フィルタに基づく条件の設定などを行っています、ここでdefaultFilterKey、defaultFilterValueをwhereして、検索条件に加えます。
Grid.php/** * @return array|Collection|mixed */ public function applyQuery() { if (!empty($this->defaultFilterKey)) { $this->model()->where($this->defaultFilterKey, $this->defaultFilterValue); } $this->applyQuickSearch(); $this->applyColumnFilter(); $this->applyColumnSearch(); $this->applySelectorQuery(); }使い方は次の通りです。ここでは$userIdがシャーディングキーになります。
$hogemodels = new HogeModel(); //シャードをセット。DBが決まる $hogemodels->setShard($userId); //Gridの初期化コールバックでシャードキーを設定。表示を$userIdのものに指定 Grid::init( function(Grid $grid) use ($userId) { $grid->addDefaultFilter('user_id', $userId); }); return Admin::content(function (Content $content) use ($hogemodels, $userId) { //Gridの利用 $content->body(Admin::grid($hogemodels, function (Grid $grid) use ($userId) { $grid->user_id('ユーザーID')->sortable(); })); });シャーディングキーを渡して表示条件指定を行う
フィルター機能にはhiddenフィールドを記述できる仕組みがあるので、それでシャーディングキーを渡すようにすると、うまく呼び出せます。
以下のようにfilterのコールバックでhidden()を記述します。$userIdがシャーディングキーです。public function resultview(Request $request) { $userId = $request->user_id; $hogemodels = new LogTrophies(); $hogemodels->setShard($userId); Grid::init( function(Grid $grid) use ($userId) { $grid->addDefaultFilter('user_id', $userId); }); return Admin::content(function (Content $content) use ($hogemodels, $userId) { $content->body(Admin::grid($hogemodels, function (Grid $grid) use ($userId) { $grid->user_id('ユーザーID')->sortable(); $grid->perPages([100, 200, 500, 1000]); $grid->filter(function ($filter) use ($userId) { $filter->disableIdFilter(); $filter->hidden('user_id', $userId); $filter->equal('type', 'type'); $filter->equal('key', 'key'); $filter->equal('value', 'value'); }); })); }); }シャーディングキーを渡してEditフォームを利用する
Editフォームにはhiddenフィールドの記述方法がないので、シャーディングキーを渡すのにちょっと頭を使います。
Editフォームを呼び出すために使うURLには、呼び出し元のURLが使われます。そこで$grid->setResourceでシャーディングキーを混ぜたURLにしてやることで、シャーディングキーを渡せます。
大元のGridで以下のようにして$content->body(Admin::grid($hogemodels, function (Grid $grid) use ($userId) { $grid->setResource('/admin/hoge/resultview/' . $userId); //Edit Action用にユーザーID渡すため $grid->user_id('ユーザーID')->sortable(); }));routes.phpは以下のようにシャーディングキーであるUserIdと、テーブルのユニークIDであるidを両方取るように記述します。
routes.php$router->get('/hoge/resultview/{userId}/{id}/edit', 'HogeController@edit');Editの受け側には、userId、idの2つの引数を取るように記述します
public function edit($userId, $id) { $hogemodels = new UserBirthdays(); $hogemodels->setShard($userId); Grid::init( function(Grid $grid) use ($userId) { $grid->addDefaultFilter('user_id', $userId); }); return Admin::content(function (Content $content) use ($hogemodels, $userId) { $content->header('hoge情報'); $content->description('編集'); $content->body( Admin::form($hogemodels, function (Form $form) use ($hogemodels, $userId) { $form->display('ユーザーID')->default($userId); $form->setaction('/admin/hoge/modification'); }) ); }); }まとめ
LaravelAdminは便利なフレームワークですが、便利であるがゆえに仕組みが決まっているため、最初は作法がわからずとっつきにくいかもしれません。でも、いろいろコードを調べると、便利なものが多くあります。ぜひいろいろ試してみてください。
明日は @hiroki_shimada の記事です。
- 投稿日:2020-11-27T17:16:24+09:00
Laravel × Vue.jsでハマった簡単なエラー
ターミナルで下記の様に表示された際は、このエラーの上で右クリック+許可するだけ
ESLint is disabled since its execution has not been approved or denied yet. Use the light bulb menu to open the approval dialog.
google先生に頼りすぎていたせいか、思考力が低下していた。気をつけよう・・・
- 投稿日:2020-11-27T15:41:47+09:00
Laravelバリデーション integerとnumericの動作の違い
- Laravel6.x
- PHP7.4
紛らわしいので調べました。
integer の挙動
入力値が整数で構成された文字列か数値であることをバリデートします。
※実際の判定文はこちら
// vendor\laravel\framework\src\Illuminate\Validation\Concerns\ValidatesAttributes.php public function validateInteger($attribute, $value) { return filter_var($value, FILTER_VALIDATE_INT) !== false; }numericの挙動
入力値が数字または数値形式の文字列であることをバリデートします。
※実際の判定文はこちら
// vendor\laravel\framework\src\Illuminate\Validation\Concerns\ValidatesAttributes.php public function validateNumeric($attribute, $value) { return is_numeric($value); }比較
- ○:バリデーションチェックを通過する
- ×:バリデーションチェックに引っかかりエラーになる
検証値 integer
numeric
regex:/^[0-9]+$/i
'0'
○ ○ ○ '123'
○ ○ ○ '0123'
× 注意! ○ ○ '+1'
○ 注意! ○ × '-1'
○ 注意! ○ × '0.1'
× ○ × '1.1'
× ○ × '113-0012'
× × × '090-111-222'
× × × '123'
× × × おまけ 動作確認用
<?php $list = ['0', '123', '0123', '+1', '-1', '0.1', '1.1', '114-0012', '090-2222-3333', '123']; foreach ($list as $value) { echo "$value "; if (filter_var($value, FILTER_VALIDATE_INT) !== false) { echo " |integer: ○"; } else { echo " |integer: ×"; } if (is_numeric($value)) { echo " |numeric: ○"; } else { echo " |numeric: ×"; } if (preg_match("/^[0-9]+$/i", $value)) { echo " |regex: ○"; } else { echo " |regex: ×"; } echo "\n"; } ?>
- 投稿日:2020-11-27T15:39:43+09:00
PHP/Laravel用語集
はじめに
PHP/Laravelに関する基本的な用語を簡単にまとめました。
特に初学者の場合、用語の意味や意図が分かっていないまま学習をしていると「今何をしてるんだ?」となりがちなので、迷子にならない為にも用語の意味を知っておくことが重要だと思い記事にしました。私自身、勉強中の身ですので間違っている箇所などありましたら、Twitter(@Tii_engineering)のDMなどでご指摘いただけたら幸いです。
随時更新予定です。
MVCフレームワーク
Model(モデル)、View(ビュー)、Controller(コントローラー)の各機能に分けて整理し、これらのパーツを作ることで開発を行う。
Routing(ルーティング)
アクセスを設定している情報(ルート)を管理する機能。
特定のアドレスにアクセスした時、どの処理を呼び出して実行するかを管理するもの。
「〇〇というアドレスにアクセスしたら、××という処理を呼び出す」という関連付けを行なっている。
Model(モデル)
データ処理全般を担当。データベースアクセスに関する処理全般を扱う。
View(ビュー)
画面表示を担当。
Controllerの指示によって アクセスしてきたユーザーのブラウザに表示するデータを生成する。(HTMLファイルを出力するところのようなイメージ)
Controller(コントローラー)
全体の制御を担当。
必要に応じてModelからデータを取得したり、Viewを利用して画面表示を作成したりする。
Blade
シンプルにHTMLファイルを書くように書くことのできるテンプレートエンジン。
通常のPHPスクリプトファイルを記述するより効率的に作ることができる。
テンプレートを継承し新たなテンプレートを定義したり、レイアウトの一部をセクションとしてはめ込むことができる。
パス(path:通り道)
ファイルやフォルダの場所。
(ファイル名を含む場合と含まない場合がある。)
Migration(マイグレーション)
データベースのバージョン管理機能。
データベースのテーブルを作成・削除等する機能も持つ。
Validation(バリデーション)
Modelでデータを保存する前に、フォームからデータを送信されてきた値が正しい形式で書かれているかどうか、データの不備をあらかじめ防ぐために検証する仕組み
Auth
facade (ファサード)という機能で、ユーザーを認証しログインを管理する機能を自動生成してくれる機能。
composer
Laravelを構成するたくさんの公開ライブラリを矛盾がない状態で管理するツール。
ライブラリ同士は相互に複雑な依存関係がるが、これらの依存関係の管理を一手に引き受けてくれるのがcomposer。
Webpack
CSSやJavaScriptなど複数に分割されているファイルを1つにまとめることができる。
ソースコードを圧縮し、接続速度が早くなる。
Laravel Mix
WebpackやsassをLaravel用に使いやすくオーバーラップしているツール。
Webpackやsassはそれぞれ環境構築などを行うのに時間がかかるがLaravel Mixを使えば設定ファイルに数行記述するだけで、自動的に圧縮等の処理ができる。
GET
指定したURLの内容を取り出すための要求で、最も基本的なHTTPメソッド。
ブラウザからURLを入力してwebページを開くときには、GETメソッドのHTTPリクエストを送っている。
POST
URLに対して情報を要求するだけでなく、クライアントからさまざまなデータを送信することができる。
主にデータを更新するような処理に使われています。
テーブル
分類されたデータベースの種類ごとの単位。
データベースは複数のテーブルを保持し、テーブルごとにデータを管理している。
カラム
データベースの列に相当するもの。「属性」を意味する。
レコード
データそのものを意味する。
インスタンス
データベースとユーザーの間を仲介する役割がある。
インスタンスを介してデータベースを操作することで、データの安全性やパフォーマンスの向上につながる。インスタンスは『データベースにアクセスするための過程』であり、データベースは『データを格納したファイル』という関係性。
スキーマ
データベースの設計図
必要なデータの洗い出しや、そのデータの格納整理のルールを決める。(全体を理解するために割り当てられた『構造』)
データベースでは、そのような作業を『スキーマを定義する』と呼ぶ。
Eloquent ORM
ORMとは、レコードをオブジェクトとして扱えるようにするための仕組み。
Laravelには「Eloquent」というORMが内臓されている。
このEloquentを使って、モデルとデータベースとのやり取りを行うようにしています。
リレーション
テーブルの関連付け。
「Aさんが投稿した記事」というように、ユーザーと記事のテーブルを関連付けることを言う。
デプロイ
公開用のサーバーを用意し、そのサーバーに作成したアプリケーションのインストールを行い本稼働させること。
- 投稿日:2020-11-27T14:58:28+09:00
Laravel6 Unitテスト時に「Maximum function nesting level~」のエラーが出た
目的
- しょーもないミスで時間を費やしてしまったので反省の意味も含めてエラー原因などをメモ的にまとめる
前提情報
- LaravelのUnitテストを実行したところ「Error: Maximum function nesting level of '256' reached, aborting!」というエラーが出た。
- テストではServiceを呼び出し、紐付いたRepositoryからデータを取得する事ができるかを確認していた。
原因
- Serviceクラスの依存注入でそのServiceクラス自体を依存注入してしまっていた。
- さらにServiceクラス内のメソッドでそのメソッドを呼び出す処理を記載していた。
わかりにくいと思うので下記に図を記載する。
上記でなんとなくご理解いただけたと思うがUnitテストで呼び出しているServiceクラスのメソッドが自身を呼び出すようになってしまっていた。
当該ループが256回に達したため、エラーを出力して処理が停止した。
改善策
- 投稿日:2020-11-27T14:02:37+09:00
【Laravel】routesのweb.phpでRouteが未定義になるのはVSCodeの拡張機能が原因だった
【Laravel】routesのweb.phpでRouteが未定義になるのはVSCodeの拡張機能が原因だった
使用環境
- windows 10 Home(COREi7)
- XAMPP 7.3.18
- Laravel 6
- VSCode
背景
Laravelの学習に取り組んでいるとルーティングを行うroutesフォルダのweb.phpでRouteが未定義だとエラーが発生していた。
原因
VSCodeの拡張機能PHP intelephenseを使用しているとこのエラーが発生する模様。
解決方法
調べてみると2パターンある模様。1つはVSCodeの拡張機能を使う方法。2つ目は定義づけする方法。
1つ目 VSCodeの拡張機能を使う
拡張機能のintelephenseを使用し、intelephenseの設定から以下のチェックを外す。その後、VSCodeを再起動する。
詳しくは下記webサイトを参照。intelephense.diagnostics.undefinedClassConstants
intelephense.diagnostics.undefinedConstants
intelephense.diagnostics.undefinedFunctions
intelephense.diagnostics.undefinedMethods
intelephense.diagnostics.undefinedProperties
intelephense.diagnostics.undefinedTypes
[VSCode] LaravelでRouteなどがエラーになるのを直す
VSCode拡張機能Intelephenseの更新によるUndefined type 'Route'(他クラス)のエラー2つ目 web.phpファイル内で定義付けする
web.phpファイル内に
use Illuminate\Support\Facades\Route;
の1文を追加する。
ちなみに関連付けされたRoute.phpは次の場所にある。
作成したLaravelプロジェクトフォルダ\vendor\laravel\framework\src\Illuminate\Support\Facades
注意事項
Routeの使用自体は
use Illuminate\Support\Facades\Route;
をわざわざ記述しなくても使用できるようになっている。web.phpでuse Illuminate\Support\Facades\Routeをしなくてよい理由
App\Http\Controllers名前空間をコントローラルート登録時に毎回指定しなくても済むように、デフォルトでRouteServiceProviderが名前空間グループの中でroutes.phpファイルを読み込み、指定していることを覚えておいてください。これにより、先頭のApp\Http\Controllers名前空間を省略でき、続きの部分を指定するだけで済みます。
[公式]ルーティング 6.xLaravel
- 投稿日:2020-11-27T14:00:25+09:00
Laraveを触って1年経ったのでTIPS
本当は2年くらいかもしれない
確認バージョンは Laravel 7.26.1
よく使うコマンド
ネームスペースとか考慮するとコマンドから作った方が良いです。でもコピペしちゃう
make系
# マイグレーション作成 php artisan make:migration create_xxxxx_table # モデル&ファクトリー&シーダー作成(app/) php artisan make:model Models/Xxxx -fs # コントローラー作成(app/Http/Controllers/) php artisan make:controller Api/XxxxController --model=Models/Xxxx --api # フォームリクエスト作成(app/Http/Requests/) php artisan make:request Api/Xxxx/Store # コマンド作成(app/Console/Commands/) php artisan make:command Xxxx ### ide-helperが導入されていれば # ide_helper:generate php artisan ide-helper:generate # ide_helper:models php artisan ide-helper:models -Ndb系
# マイグレーション実行 php artisan migrate # マイグレーションを一つ戻す php artisan migrate:rollback --step=1 # DB初期化&DatabaseSeeder実行 php artisan migrate:fresh --seed # シーダー実行 php artisan db:seed --class=UserSeederキャッシュを消す
# 全消し php artisan optimize:clear # optimize:clearの中身の実態は下記コマンド php artisan view:clear && \ php artisan cache:clear && \ php artisan route:clear && \ php artisan config:clear && \ php artisan clear-compiledちなみに間違ってもローカル開発環境で
php artisan config:cache
は使わないよう注意してください。
phpunitを走らせた際にキャッシュ化した.envの環境変数が使われてしまいます。
間違えた場合はphp artisan config:clear
してください。公式にも書いてる!
https://readouble.com/laravel/7.x/ja/configuration.htmlFactory
公式Faker
https://github.com/fzaninotto/Faker日本語ソース
https://github.com/fzaninotto/Faker/blob/master/src/Faker/Provider/ja_JP# ランダム英数字生成(unique使っても衝突してしまう場合) $faker->unique()->regexify('[a-zA-Z0-9]{1,20}')Routing
- Laravelでapiを叩いたときにjsonが返ってこない問題
php artisan route:list > route.txt
で吐き出すと楽ルーディングの書き方について
個人的にはHTTPメソッドごとにアクションを指定した方がいいかと思います。
Route::resourceを使ってデフォルトアクションを指定できるんですが、ワナがあります。
下記のように公開・下書きで投稿の処理を丸ごと分けたいなどした時に下記のようなルーティングになるかと思います。api.php
Route::resource('publish/posts', 'Publish\PostController')->only([ 'index', 'store', 'show', 'update', 'destroy', ]); Route::resource('draft/posts', 'Draft\PostController')->only([ 'index', 'store', 'show', 'update', 'destroy', ]);処理的には問題ないですが、
php artisan route:cache
(本番環境での高速化に必要)した際にname被りでエラーになります。
なのでシンプルにメソッドごとに書いた方が良いかと思います(いずれresourceで表現できないURIの方が多くなるので)Route::get('/publish/posts', 'Publish\PostController@index'); Route::post('/publish/posts', 'Publish\PostController@store'); Route::get('/publish/posts/{id}', 'Publish\PostController@show'); Route::patch('/publish/posts/{id}', 'Publish\PostController@update'); Route::delete('/publish/posts/{id}', 'Publish\PostController@destroy'); Route::get('/draft/posts', 'Draft\PostController@index'); Route::post('/draft/posts', 'Draft\PostController@store'); Route::get('/draft/posts/{id}', 'Draft\PostController@show'); Route::patch('/draft/posts/{id}', 'Draft\PostController@update'); Route::delete('/draft/posts/{id}', 'Draft\PostController@destroy');Controller
- ルートモデルバインディングはFatControllerになる危険性があるので、使用は注意
FormRequest
- exists使う際にはdeleted_atを考慮する必要あり
- min、max、sizeなどは型指定で動きが変わるので注意
- existsの逆はuniqueを使う
public function rules() { return [ 'id' => 'required|integer|exists:posts,id,deleted_at,NULL', 'str1' => 'required|string|max:10', // 10文字以内 'str2' => 'required|string|min:10', // 10文字以上 'str3' => 'required|string|size:10', // 10文字固定 'num1' => 'required|integer|max:10', // 10以内 'num2' => 'required|integer|min:10', // 10以上 'num3' => 'required|integer|size:10', // 10固定 ]; }Eloquent
Model
- 論理削除を有効にしたい場合は
use SoftDeletes
- 全モデルでguardedにidを指定しておくと良い。予期せぬキーの登録・更新を防げる。
- created_atカラムを作成しなかった場合は
CREATED_AT = null
- updated_atカラムを作成しなかった場合は
UPDATED_AT = null
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Xxxxx extends Model { use SoftDeletes; const CREATED_AT = null; const UPDATED_AT = null; protected $guarded = [ 'id', ]; }Relation
こんなモデルがあったとして
class User extends Authenticatable { public function posts() { return $this->hasMany(Post::class); } public function comments() { return $this->hasMany(Comment::class); } }class Post extends Model { use SoftDeletes; public function user() { return $this->belongsTo(User::class); } public function comments() { return $this->hasMany(Comment::class); } }class Comment extends Model { const UPDATED_AT = null; public function user() { return $this->belongsTo(User::class); } public function post() { return $this->belongsTo(Post::class); } }リレーションメソッドでアクセスした時
- hasOneの戻り値は Model or null
- hasManyの戻り値はCollection
- belongsToの戻り値は Model or null
QueryBuilder
- クエリビルダの場合はjoinするテーブルのdeleted_atが機能しない
User::join('posts', 'users.id', '=', 'posts.user_id')->get();select * from users inner join posts on users.id = posts.user_id
- joinされるテーブルがModelの場合はdeleted_atが機能する
Post::join('comments', 'posts.id', '=', 'comments.post_id')->get();select * from posts inner join comments on posts.id = comments.post_id where posts.deleted_at is nullRelation Query
hasで流れるSQL
User::has('posts.comments')->get();[2020-12-05 23:39:48] local.DEBUG: SQL {"time":"12.82 ms","sql":"select * from `users` where exists (select * from `posts` where `users`.`id` = `posts`.`user_id` and exists (select * from `comments` where `posts`.`id` = `comments`.`post_id`) and `posts`.`deleted_at` is null)"}select * from users where exists ( select * from posts where users.id = posts.user_id and exists ( select * from comments where posts.id = comments.post_id ) and posts.deleted_at is null )joinで流れるSQL
User::join('posts', 'posts.user_id', '=', 'users.id') ->join('comments', 'comments.post_id', '=', 'posts.id') ->whereNull('posts.deleted_at') ->get();[2020-12-06 00:00:13] local.DEBUG: SQL {"time":"12.11 ms","sql":"select * from `users` inner join `posts` on `posts`.`user_id` = `users`.`id` inner join `comments` on `comments`.`post_id` = `posts`.`id` where `posts`.`deleted_at` is null"}select * from users inner join posts on posts.user_id = users.id inner join comments on comments.post_id = posts.id where posts.deleted_at is nullwithで流れるSQL
User::with('posts.comments')->get();[2020-12-05 23:49:37] local.DEBUG: SQL {"time":"11.54 ms","sql":"select * from `users`"} [2020-12-05 23:49:37] local.DEBUG: SQL {"time":"0.70 ms","sql":"select * from `posts` where `posts`.`user_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) and `posts`.`deleted_at` is null"} [2020-12-05 23:49:37] local.DEBUG: SQL {"time":"0.41 ms","sql":"select * from `comments` where `comments`.`post_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)"}select * from users select * from posts where posts.user_id in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) and posts.deleted_at is null select * from comments where comments.post_id in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)Console
複数時間でタスクスケジューリングしたい場合(8:30、11:30、18:30など)はcron形式にする。
app/Console/Kernel.php
->cron('30 8,11,18 * * *');コンソール出力時にログにも残す
トレイトを作成してコマンドクラスで
use PrependsOutput;
してあげればOK
普通に$this->info()
や$this->error()
するだけでログに残せます。
テスト時に実行時間を出力していない理由は、コンソール出力値もテストしているためです。app/Console/Commands/PrependsOutput.php
<?php namespace App\Console\Commands; use Carbon\CarbonImmutable; trait PrependsOutput { /** * コンソール出力に追加 * * @param string $string * @param string|null $style * @param int|string|null $verbosity * @return void */ public function line($string, $style = null, $verbosity = null) { if (\App::environment() === 'testing') { parent::line($string, $style, $verbosity); } else { parent::line(CarbonImmutable::now()->format('[Y-m-d H:i:s] ').$string, $style, $verbosity); } logger(CarbonImmutable::now()->format('[Y-m-d H:i:s] ').$string); } }PHPUnitテスト
- テストが遅い時はxdebugを無効にしてみてください。5倍くらい違う!
# テスト実行 ./vendor/bin/phpunit # 対象のファイルでテスト実行 ./vendor/bin/phpunit tests/Feature/XxxxxTest.php # 対象のファイルの対象の関数でテスト実行 ./vendor/bin/phpunit tests/Feature/XxxxxTest.php --filter=xxxxxxxx # テスト結果をログに残す ./vendor/bin/phpunit --testdox-text=test.txt他いろいろ
実行SQLを確認したい
下記を参考にしてちょっと直しました
LaravelでSQL文をlaravel.logに出力する下記をregisterに追加
app/Providers/AppServiceProvider.php
/** * Register any application services. * * @return void */ public function register() { // SQL Log \DB::listen(function ($query) { $sql = preg_replace('/"(.*?)"/', "'$1'", $query->sql); for ($i = 0; $i < count($query->bindings); $i++) { $bindValue = $query->bindings[$i]; if (is_bool($bindValue)) { $bindValue = $bindValue ? 'true' : 'false'; } else { $bindValue = "'".(string)$bindValue."'"; } $sql = preg_replace("/\?/", $bindValue, $sql, 1); } \Log::debug("SQL", ["time" => sprintf("%.2f ms", $query->time), "sql" => $sql]); }); }Laravelログ+Slackにもログを残す
- stackのchannelsにslackを追加
- slackのlevelをcritical → debugに変更
- envのLOG_SLACK_WEBHOOK_URLにslackのwebhookUrlを指定
config/logging.php
'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => [ 'single', 'slack', // 追加 ], 'ignore_exceptions' => false, ], 〜 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => 'debug', // critical → debugに変更 ],.env
LOG_SLACK_WEBHOOK_URL=xxxxxxstackのchannelsには複数のdriverが使えるので、fatalエラーと単純なログをチャンネルごとに分けると便利です
'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => [ 'single', 'slack-critical', 'slack-debug', ], 'ignore_exceptions' => false, ], 〜 'slack-critical' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel critical Log', 'emoji' => ':boom:', 'channel' => env('LOG_SLACK_CHANNEL_ALERT'), 'level' => 'critical', ], 'slack-debug' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel debug Log', 'emoji' => ':memo:', 'channel' => env('LOG_SLACK_CHANNEL_DEBUG'), 'level' => 'debug', ],
- 投稿日:2020-11-27T14:00:25+09:00
Laravelを触って1年経ったのでTIPS
本当は2年くらいかもしれない
確認バージョンは Laravel 7.26.1
よく使うコマンド
ネームスペースとか考慮するとコマンドから作った方が良いです。でもコピペしちゃう
make系
# マイグレーション作成 php artisan make:migration create_xxxxx_table # モデル&ファクトリー&シーダー作成(app/) php artisan make:model Models/Xxxx -fs # コントローラー作成(app/Http/Controllers/) php artisan make:controller Api/XxxxController --model=Models/Xxxx --api # フォームリクエスト作成(app/Http/Requests/) php artisan make:request Api/Xxxx/Store # コマンド作成(app/Console/Commands/) php artisan make:command Xxxx ### ide-helperが導入されていれば # ide_helper:generate php artisan ide-helper:generate # ide_helper:models php artisan ide-helper:models -Ndb系
# マイグレーション実行 php artisan migrate # マイグレーションを一つ戻す php artisan migrate:rollback --step=1 # DB初期化&DatabaseSeeder実行 php artisan migrate:fresh --seed # シーダー実行 php artisan db:seed --class=UserSeederキャッシュを消す
# 全消し php artisan optimize:clear # optimize:clearの中身の実態は下記コマンド php artisan view:clear && \ php artisan cache:clear && \ php artisan route:clear && \ php artisan config:clear && \ php artisan clear-compiledちなみに間違ってもローカル開発環境で
php artisan config:cache
は使わないよう注意してください。
phpunitを走らせた際にキャッシュ化した.envの環境変数が使われてしまいます。
間違えた場合はphp artisan config:clear
してください。公式にも書いてる!
https://readouble.com/laravel/7.x/ja/configuration.htmlFactory
公式Faker
https://github.com/fzaninotto/Faker日本語ソース
https://github.com/fzaninotto/Faker/blob/master/src/Faker/Provider/ja_JP# ランダム英数字生成(unique使っても衝突してしまう場合) $faker->unique()->regexify('[a-zA-Z0-9]{1,20}')Routing
- Laravelでapiを叩いたときにjsonが返ってこない問題
php artisan route:list > route.txt
で吐き出すと楽ルーディングの書き方について
個人的にはHTTPメソッドごとにアクションを指定した方がいいかと思います。
Route::resourceを使ってデフォルトアクションを指定できるんですが、ワナがあります。
下記のように公開・下書きで投稿の処理を丸ごと分けたいなどした時に下記のようなルーティングになるかと思います。api.php
Route::resource('publish/posts', 'Publish\PostController')->only([ 'index', 'store', 'show', 'update', 'destroy', ]); Route::resource('draft/posts', 'Draft\PostController')->only([ 'index', 'store', 'show', 'update', 'destroy', ]);処理的には問題ないですが、
php artisan route:cache
(本番環境での高速化に必要)した際にname被りでエラーになります。
なのでシンプルにメソッドごとに書いた方が良いかと思います(いずれresourceで表現できないURIの方が多くなるので)Route::get('/publish/posts', 'Publish\PostController@index'); Route::post('/publish/posts', 'Publish\PostController@store'); Route::get('/publish/posts/{id}', 'Publish\PostController@show'); Route::patch('/publish/posts/{id}', 'Publish\PostController@update'); Route::delete('/publish/posts/{id}', 'Publish\PostController@destroy'); Route::get('/draft/posts', 'Draft\PostController@index'); Route::post('/draft/posts', 'Draft\PostController@store'); Route::get('/draft/posts/{id}', 'Draft\PostController@show'); Route::patch('/draft/posts/{id}', 'Draft\PostController@update'); Route::delete('/draft/posts/{id}', 'Draft\PostController@destroy');Controller
- ルートモデルバインディングはFatControllerになる危険性があるので、使用は注意
FormRequest
- exists使う際にはdeleted_atを考慮する必要あり
- min、max、sizeなどは型指定で動きが変わるので注意
- existsの逆はuniqueを使う
public function rules() { return [ 'id' => 'required|integer|exists:posts,id,deleted_at,NULL', 'str1' => 'required|string|max:10', // 10文字以内 'str2' => 'required|string|min:10', // 10文字以上 'str3' => 'required|string|size:10', // 10文字固定 'num1' => 'required|integer|max:10', // 10以内 'num2' => 'required|integer|min:10', // 10以上 'num3' => 'required|integer|size:10', // 10固定 ]; }Eloquent
Model
- 論理削除を有効にしたい場合は
use SoftDeletes
- 全モデルでguardedにidを指定しておくと良い。予期せぬキーの登録・更新を防げる。
- created_atカラムを作成しなかった場合は
CREATED_AT = null
- updated_atカラムを作成しなかった場合は
UPDATED_AT = null
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Xxxxx extends Model { use SoftDeletes; const CREATED_AT = null; const UPDATED_AT = null; protected $guarded = [ 'id', ]; }Relation
こんなモデルがあったとして
class User extends Authenticatable { public function posts() { return $this->hasMany(Post::class); } public function comments() { return $this->hasMany(Comment::class); } }class Post extends Model { use SoftDeletes; public function user() { return $this->belongsTo(User::class); } public function comments() { return $this->hasMany(Comment::class); } }class Comment extends Model { const UPDATED_AT = null; public function user() { return $this->belongsTo(User::class); } public function post() { return $this->belongsTo(Post::class); } }リレーションメソッドでアクセスした時
- hasOneの戻り値は Model or null
- hasManyの戻り値はCollection
- belongsToの戻り値は Model or null
QueryBuilder
- クエリビルダの場合はjoinするテーブルのdeleted_atが機能しない
User::join('posts', 'users.id', '=', 'posts.user_id')->get();select * from users inner join posts on users.id = posts.user_id
- joinされるテーブルがModelの場合はdeleted_atが機能する
Post::join('comments', 'posts.id', '=', 'comments.post_id')->get();select * from posts inner join comments on posts.id = comments.post_id where posts.deleted_at is nullRelation Query
- hasは1階層と2階層両方書く必要はない
- ちなみにwithは1階層と2階層両方書いても実行されるSQLは変わらない
hasで流れるSQL
User::has('posts.comments')->get();[2020-12-05 23:39:48] local.DEBUG: SQL {"time":"12.82 ms","sql":"select * from `users` where exists (select * from `posts` where `users`.`id` = `posts`.`user_id` and exists (select * from `comments` where `posts`.`id` = `comments`.`post_id`) and `posts`.`deleted_at` is null)"}select * from users where exists ( select * from posts where users.id = posts.user_id and exists ( select * from comments where posts.id = comments.post_id ) and posts.deleted_at is null )joinで流れるSQL
User::join('posts', 'posts.user_id', '=', 'users.id') ->join('comments', 'comments.post_id', '=', 'posts.id') ->whereNull('posts.deleted_at') ->get();[2020-12-06 00:00:13] local.DEBUG: SQL {"time":"12.11 ms","sql":"select * from `users` inner join `posts` on `posts`.`user_id` = `users`.`id` inner join `comments` on `comments`.`post_id` = `posts`.`id` where `posts`.`deleted_at` is null"}select * from users inner join posts on posts.user_id = users.id inner join comments on comments.post_id = posts.id where posts.deleted_at is nullwithで流れるSQL
User::with('posts.comments')->get();[2020-12-05 23:49:37] local.DEBUG: SQL {"time":"11.54 ms","sql":"select * from `users`"} [2020-12-05 23:49:37] local.DEBUG: SQL {"time":"0.70 ms","sql":"select * from `posts` where `posts`.`user_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) and `posts`.`deleted_at` is null"} [2020-12-05 23:49:37] local.DEBUG: SQL {"time":"0.41 ms","sql":"select * from `comments` where `comments`.`post_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)"}select * from users select * from posts where posts.user_id in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) and posts.deleted_at is null select * from comments where comments.post_id in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)Console
複数時間でタスクスケジューリングしたい場合(8:30、11:30、18:30など)はcron形式にする。
app/Console/Kernel.php
->cron('30 8,11,18 * * *');コンソール出力時にログにも残す
トレイトを作成してコマンドクラスで
use PrependsOutput;
してあげればOK
普通に$this->info()
や$this->error()
するだけでログに残せます。
テスト時に実行時間を出力していない理由は、コンソール出力値もテストしているためです。app/Console/Commands/PrependsOutput.php
<?php namespace App\Console\Commands; use Carbon\CarbonImmutable; trait PrependsOutput { /** * コンソール出力に追加 * * @param string $string * @param string|null $style * @param int|string|null $verbosity * @return void */ public function line($string, $style = null, $verbosity = null) { if (\App::environment() === 'testing') { parent::line($string, $style, $verbosity); } else { parent::line(CarbonImmutable::now()->format('[Y-m-d H:i:s] ').$string, $style, $verbosity); } logger(CarbonImmutable::now()->format('[Y-m-d H:i:s] ').$string); } }PHPUnitテスト
- テストが遅い時はxdebugを無効にしてみてください。5倍くらい違う!
# テスト実行 ./vendor/bin/phpunit # 対象のファイルでテスト実行 ./vendor/bin/phpunit tests/Feature/XxxxxTest.php # 対象のファイルの対象の関数でテスト実行 ./vendor/bin/phpunit tests/Feature/XxxxxTest.php --filter=xxxxxxxx # テスト結果をログに残す ./vendor/bin/phpunit --testdox-text=test.txt他いろいろ
実行SQLを確認したい
下記を参考にしてちょっと直しました
LaravelでSQL文をlaravel.logに出力する下記をregisterに追加
app/Providers/AppServiceProvider.php
/** * Register any application services. * * @return void */ public function register() { // SQL Log \DB::listen(function ($query) { $sql = preg_replace('/"(.*?)"/', "'$1'", $query->sql); for ($i = 0; $i < count($query->bindings); $i++) { $bindValue = $query->bindings[$i]; if (is_bool($bindValue)) { $bindValue = $bindValue ? 'true' : 'false'; } else { $bindValue = "'".(string)$bindValue."'"; } $sql = preg_replace("/\?/", $bindValue, $sql, 1); } \Log::debug("SQL", ["time" => sprintf("%.2f ms", $query->time), "sql" => $sql]); }); }Laravelログ+Slackにもログを残す
- stackのchannelsにslackを追加
- slackのlevelをcritical → debugに変更
- envのLOG_SLACK_WEBHOOK_URLにslackのwebhookUrlを指定
config/logging.php
'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => [ 'single', 'slack', // 追加 ], 'ignore_exceptions' => false, ], 〜 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => 'debug', // critical → debugに変更 ],.env
LOG_SLACK_WEBHOOK_URL=xxxxxxstackのchannelsには複数のdriverが使えるので、fatalエラーと単純なログをチャンネルごとに分けると便利です
'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => [ 'single', 'slack-critical', 'slack-debug', ], 'ignore_exceptions' => false, ], 〜 'slack-critical' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel critical Log', 'emoji' => ':boom:', 'channel' => env('LOG_SLACK_CHANNEL_ALERT'), 'level' => 'critical', ], 'slack-debug' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel debug Log', 'emoji' => ':memo:', 'channel' => env('LOG_SLACK_CHANNEL_DEBUG'), 'level' => 'debug', ],
- 投稿日:2020-11-27T13:21:28+09:00
Laravelでapiを叩いたときにjsonが返ってこない問題
確認時のLaravelバージョンは7.26.1
apiを叩いたときにjsonが返ってこない問題
結論から言うとapiを叩く際に下記ヘッダーを付与しなければならない
Accept: application/json or X-Requested-With: XMLHttpRequest個人的には
X-Requested-With: XMLHttpRequest
で良いかと思います。view側でaxios使用時に使われているのでresources/js/bootstrap.js
window.axios = require('axios'); window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';原因調べてみた
ソースを追っていくとHandler::expectsJsonがfalseになってしまうのが原因
1. app/Exceptions/Handler::render 2. vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler::render 3. vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler::expectsJsonHandler::expectsJsonの実装を見るとこんな感じ
public function expectsJson() { return ($this->ajax() && ! $this->pjax() && $this->acceptsAnyContentType()) || $this->wantsJson(); }各メソッドを引っ張ってくるとこんな感じ
public function ajax() { return $this->isXmlHttpRequest(); } public function isXmlHttpRequest() { return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); } public function pjax() { return $this->headers->get('X-PJAX') == true; } public function acceptsAnyContentType() { $acceptable = $this->getAcceptableContentTypes(); return count($acceptable) === 0 || ( isset($acceptable[0]) && ($acceptable[0] === '*/*' || $acceptable[0] === '*') ); } public function getAcceptableContentTypes() { if (null !== $this->acceptableContentTypes) { return $this->acceptableContentTypes; } return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); } public function wantsJson() { $acceptable = $this->getAcceptableContentTypes(); return isset($acceptable[0]) && Str::contains($acceptable[0], ['/json', '+json']); }これ外部のapiクライアント使った時のハマりポイントですね
公式ドキュメントに乗っけて欲しい。。。
- 投稿日:2020-11-27T13:18:00+09:00
【Laravel】validationのエラーメッセージ「The given data was invalid.」をカスタマイズする
確認バージョンは Laravel 7.26.1
FormRequestのバリデーションを追って見るとこんな感じ
1. config/app.phpのproviders 2. Illuminate\Foundation\Providers\FoundationServiceProvider::class 3. vendor/laravel/framework/src/Illuminate/Foundation/Providers/FormRequestServiceProvider::boot 4. vendor/laravel/framework/src/Illuminate/Validation/ValidatesWhenResolvedTrait::validateResolved 5. vendor/laravel/framework/src/Illuminate/Validation/ValidatesWhenResolvedTrait::failedValidation 6. vendor/laravel/framework/src/Illuminate/Validation/ValidationException::__constructvendor/laravel/framework/src/Illuminate/Validation/ValidationException::__construct
でexceptionのメッセージをThe given data was invalid.
でハードコーディングしているのが問題であるなので
app/Exceptions/Handler.php
をカスタマイズする。
HandlerはIlluminate\Foundation\Exceptions\Handler
を継承しているので、そのソースを参考に下記メソッドをオーバーライド/** * Convert a validation exception into a JSON response. * * @param \Illuminate\Http\Request $request * @param \Illuminate\Validation\ValidationException $exception * @return \Illuminate\Http\JsonResponse */ protected function invalidJson($request, ValidationException $exception) { return response()->json([ 'message' => $exception->getMessage() === 'The given data was invalid.' ? '指定されたデータは無効でした。' : $exception->getMessage(), 'errors' => $exception->errors(), ], $exception->status); }うーん…ハードコーディングしてる方が悪いと思う。
- 投稿日:2020-11-27T13:14:03+09:00
【Laravel】Eloquentのhasは1階層と2階層両方書く必要はない
確認バージョンは Laravel 7.26.1
例えばこんなモデルがあったとすると
class User extends Authenticatable { /** * Relations */ public function posts() { return $this->hasMany(Post::class); } public function comments() { return $this->hasMany(Comment::class); } }class Post extends Model { use SoftDeletes; /** * Relations */ public function user() { return $this->belongsTo(User::class); } public function comments() { return $this->hasMany(Comment::class); } }class Comment extends Model { const UPDATED_AT = null; /** * Relations */ public function user() { return $this->belongsTo(User::class); } public function post() { return $this->belongsTo(Post::class); } }一階層のhas
User::has('posts')->get();select * from users where exists ( select * from posts where users.id = posts.user_id and posts.deleted_at is null )二階層のhas
User::has('posts.comments')->get();select * from users where exists ( select * from posts where users.id = posts.user_id and exists ( select * from comments where posts.id = comments.post_id ) and posts.deleted_at is null )一階層&二階層のhas
User::has('posts') ->has('posts.comments') ->get();select * from users where exists ( select * from posts where users.id = posts.user_id and posts.deleted_at is null ) and exists ( select * from posts where users.id = posts.user_id and exists ( select * from comments where posts.id = comments.post_id ) and posts.deleted_at is null )上記の通り、一階層と二階層両方書く必要はないです!
- 投稿日:2020-11-27T11:55:43+09:00
Laravel 7 Socialiteを使ってLINE認証を実装してみる。
はじめに
Laravel7でLINE認証機能を実装しました。
記事自体はポチポチあるけど、古い記事が多いしやり方も色々あってなんかうまくいかない...。
少し手間取りましたが、無事実装できたので備忘録として纏めます。偉大な参考記事
ほとんど下記URLの作成者様のコーディングを参考にして書いております。
https://tdomy.com/2020/08/how-to-use-laravel-socialite/上記記事に加えてLINEのDevelopersの詳細等を補足して説明します。
上記記事ではLaravel6っぽいです。
私のはLaravel7でやってみます。実装環境
・XAMPP
・Windows10
・PHP 7.4.11
・Laravel 7.31LINE Developersで諸々登録
まずLINE Developersに登録してアプリで使うidやらなんやらを取得します。
登録したら早速プロバイダーを作成してみましょう。名前は何でもオッケーです。
そしたらLINEログインチャンネルを作成しましょう。
チャネル作成の画面でチャネル名だのなんだのを設定できます。
ここでの名前は自分の覚えやすい名前等で登録します。・チャネルタイプはLINE Loginを選択
・アプリの種類は該当のものにチェック
その他は埋めるところは埋めましょう。チャネルを作成できたら上記画面のようになります。
このページでチャネルIDとチャネルシークレットは後々使用します。
ここのLINEログインをクリックしてコールバックURLを設定しましょう。
URLはhttps://localhost/アプリ名/public/login/line/callback みたいな感じで。それでは次はログインで使用するLINEアカウントに権限をつけましょう。
LINE Developersで登録したアカウントを今回ログインで使用するものと同じであればやらなくておkです。
ビジネスアカウントとかで登録している場合は下記をしないとエラーになるのでやりましょう。これで準備オッケー!
それではLaravelに移りましょー。Socialiteを導入
Laravelのcreateからauthの導入は省きます。
それでは下記コマンドでsocialiteを入れましょう。$composer require laravel/socialiteこれでfacebookやgoogleとかは使えるのですが、
残念ながらLINEは対象外...。
ということで追加でLINE専用プロパイダーを入れましょう。$ composer require socialiteproviders/lineこれでプロパイダーはオッケー。次は環境変数ですね。
環境変数を入れる
先ほどLINE Developersで取得したIDだのを記載します。
config/service.php'line' => [ 'client_id' => env('LINE_CLIENT_ID'), 'client_secret' => env('LINE_CLIENT_SECRET'), 'redirect' => env('LINE_CLIENT_CALLBACK'), ],.envLINE_CLIENT_ID=ここにclientIDを入れる LINE_CLIENT_SECRET=ここにシークレットIDを入れる LINE_CLIENT_CALLBACK=/login/line/callbackこんな感じでオッケーです。
プロパイダー追加の記述
LINEのように別途でプロパイダーを追加した場合は下記の記述をします。
config/app.php'providers' => [ // デフォルトでいろいろ書かれてます SocialiteProviders\Manager\ServiceProvider::class, // ここを追記 ];もういっちょ
app/Providers/EventServiceProvider.phpprotected $listen = [ // デフォルトでなんかいろいろ書かれてる \SocialiteProviders\Manager\SocialiteWasCalled::class => [ 'SocialiteProviders\\Line\\LineExtendSocialite@handle', //ここの二行を追記 ], ];よっしゃこれでSocialiteを使う準備オッケー!
Usersテーブルを変更する
LaravelではデフォルトでUsersテーブルがありますが、
今回のユーザー新規登録ではname,provider,provided_user_idで登録させるとします。migrations/2014_10_12_000000_create_users_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->enum('provider', ['line', 'twitter']); $table->string('provided_user_id'); $table->timestamps(); $table->unique(['provider', 'provided_user_id']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } }編集し終えたら忘れずにmigrateします。
Userモデルを実装
Userモデルもデフォルトであるのでそれを使います。
app/User.php<?php namespace App; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; use Illuminate\Auth\Authenticatable; class User extends Model implements AuthenticatableContract { use Authenticatable, Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'provider', 'provided_user_id', ]; /** * Get the password for the user. * * @return string */ public function getAuthPassword() { // We don't use password login. return ''; } /** * Get the column name for the "remember me" token. * * @return string */ public function getRememberTokenName() { // We don't use this. return ''; } }これでモデルもオッケーです。
次はコントローラー記述です。コントローラー実装
Authを作ったときに自動でLoginControllerが作られるのでそれを使います。
app/Http/Controllers/Auth/LoginController.php<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Support\Facades\Auth; //追記 use Laravel\Socialite\Facades\Socialite; //追記 use App\User; //追記 class LoginController extends Controller { /* |-------------------------------------------------------------------------- | Login Controller |-------------------------------------------------------------------------- | | This controller handles authenticating users for the application and | redirecting them to your home screen. The controller uses a trait | to conveniently provide its functionality to your applications. | */ use AuthenticatesUsers; /** * Where to redirect users after login. * * @var string */ protected $redirectTo = RouteServiceProvider::HOME; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest')->except('logout'); } /**ここから下が追記 */ /** * Redirect the user to the provider authentication page. * * @param string $provider * @return \Illuminate\Http\Response */ public function redirectToProvider($provider) { return Socialite::driver($provider)->redirect(); } /** * Obtain the user information from the provider. * * @param string $provider * @return \Illuminate\Http\Response */ public function handleProviderCallback($provider) { $provided_user = Socialite::driver($provider)->user(); $user = User::where('provider', $provider) ->where('provided_user_id', $provided_user->id) ->first(); if ($user === null) { // redirect confirm $user = User::create([ 'name' => $provided_user->name, 'provider' => $provider, 'provided_user_id' => $provided_user->id, ]); } Auth::login($user); return redirect()->route('home'); } /** * Log the user out of the application. * * @return \Illuminate\Http\Response */ public function logout() { Auth::logout(); return redirect()->route('home'); } }return redirect先のパスは各自好きなrouteを指定してください。
今回はデフォルトでルート付けされているhome
にしています。ルーティングを作る
さて、最後にloginとlogoutに関するルーティングをして終わりです!
routes/web.php<?php use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', 'HomeController@index')->name('home'); Route::get('/login/{provider}', 'Auth\LoginController@redirectToProvider'); Route::get('/login/{provider}/callback', 'Auth\LoginController@handleProviderCallback'); Route::get('/logout', 'Auth\LoginController@logout')->name('logout');これでルート付けもできた!ということでLineログインまで飛んでみましょう。
今回はURLで飛んでみます。
https://locallhost/アプリ名/public/login/line で飛んでみると....。ここでメアドとパスワードを入れてみる。
You are logged in!!!!!!!!!!!!!!!!!
やったー!ログインできた!ついでにMySQLを見るとしっかりidが追加されてました!感想
Laravel7の記事がとても少ないし、LINEのプロパイダー記事も古い記事が多く割と躓きました。
参考記事の方はlaravel6だったのですが、同じ方法で7もできました。
他のプロパイダーを追加した場合の記事も完成次第投稿したいと思います。
- 投稿日:2020-11-27T10:54:10+09:00
Docker でphp8とlaravel8を動かす
はじめに
php8が公開されたので触ってみたい興味でDockerでの環境を作ってみました。
公式のDocker hubにはまだrc版しかなかったので、今回はrc版で作ってます。使い方
↑のgithubからソースをクローンして、ディレクトリに移動してください。
入ったディレクトリでmake set-up
を入力したら環境が出来上がると思います。
終わったらhttp://127.0.0.1:8000/ にアクセスして↓の様になっていれば完成です。
詳しい説明はそのうち書くかもしれません
おわりに
今回の記事を参考にphp8をどんどん触っていけたらいいなと思います。
間違いが合ったりしたら、コメントやPRお願いします!
- 投稿日:2020-11-27T07:25:54+09:00
Laravel 6.x 非同期通信(Ajax) 【JavaScript】 【jQuery】 【axios】 【Vue.js】 各記述方法 ~jQuery編~
制作環境
Windows 10
Laravel : 6.18.35
Laravel/ui : 1.0
Laravel-mix : 5.0.1
Bootstrap : 4.0.0
axios : 0.19
Vue : 2.5.17
XAMPP
PHP : 7.4.3
Visual Studio Codeはじめに
この記事はプログラミングをはじめたばかりの素人が、できたことをメモするのに利用しています。
内容には誤りがあるかもしれません。【2020/11/27 古い書き方での記述だったので、書き方を変更しました。】
前回のつづきになります。
関連記事
Laravel 6.x 非同期通信(Ajax) 【JavaScript】 【jQuery】 【axios】 【Vue.js】 各記述方法 ~事前準備編~
Laravel 6.x 非同期通信(Ajax) 【JavaScript】 【jQuery】 【axios】 【Vue.js】 各記述方法 ~JavaScript編~今回作成するもの
非同期通信(Ajax)を利用し、自動で仕入れ先が入力される機能を実装します。
前回JavaScriptで作成した内容を、jQueryで作成したいと思います。以下作成条件
- 記述にはjQueryを使用します。
- 数字が4桁入力されたら自動で仕入れ先を検索し、反映します。
- 検索するのにクリック操作を必要としません。
input
に入力されたら検索します。- スクリプトの読み込みには
Laravel-mix
を使用します。- 前回作成した
test.js
を編集して使用するで、必要ならtest2.js
等コピーを作成してバックアップしてください。完成イメージ
スクリプトの作成
resources>js
内のtest.js
を開き、以下のように記述します。
※前回のJavaScriptの内容は全て削除してください。以下の記述方法は古い書き方だったので、記述を変更しました。
記述変更前
test.js$(function() { // inputのid="code"(業者コード)の要素を取得し、inputイベントを監視します // 業者コードに値が入力されたら以下の内容を実行します $('#code').on('input', function() { // 入力された値を変数traderCodeに代入します let traderCode = $('#code').val(); // 入力された値が4桁未満の時は検索させないようにします if (traderCode.length < 4) { return; } // 非同期通信を開始します $.ajax({ // アクセスするurlを設定します url: '/form_search?trader_code=' + traderCode, // アクセスの方法を設定します type: 'GET', // コントローラから受け取ったデータ(検索結果)をdataに代入し以下の処理を実行します success: function(data) { // 受け取ったデータ(検索結果)を仕入れ先のvalueに反映します $('#supplier').val(data[0].trader_name); } }) }) })記述変更後
test.js$(function() { // inputのid="code"(業者コード)の要素を取得し、inputイベントを監視します // 業者コードに値が入力されたら以下の内容を実行します $('#code').on('input', function() { // 入力された値を変数traderCodeに代入します let traderCode = $('#code').val(); // 入力された値が4桁未満の時は検索させないようにします if (traderCode.length < 4) { return; } // 非同期通信を開始します $.ajax({ // アクセスするurlを設定します url: '/form_search?trader_code=' + traderCode, // アクセスの方法を設定します type: 'GET', // コントローラから受け取ったデータ(検索結果)をdataに代入し以下の処理を実行します // successの代わりにdoneを使用します }).done(function(data) { // 受け取ったデータ(検索結果)を仕入れ先のvalueに反映します $('#supplier').val(data[0].trader_name); }) }) })コンパイル
ターミナルを起動し、以下を実行してください。
※node.jsが必要です。インストールされていない方は、インストールを行ってください。node.js
https://nodejs.org/ja/npm run dev確認
その他は
JavaScript
の時に作成したものがそのまま使えるので、実際にJavaScript
の時と同じ動作をするか確認してみてください。$.ajaxについて
console.log
で$.ajax
を確認すると、以下のようなデータが返ってきているのがわかります。
responseJSON
とresponseText
に目的のデータがあるのがわりますが、responseText
の方はJSON.parseされていないので、業者名がおかしいです。success: function(data)について この書き方は古いので、doneを使用した記述にしてください。
前回の
JavaScript
ではresponseText
を使用してコントローラからデータを受け取りましたが、今回は引数に返り値を代入したい変数名を入れるだけで、データを受け取れます。
data
の部分は好きな値で大丈夫です。
console.log(data)
で確認すると、欲しい値が代入されているのがわかります。.done(function(data)について
.done
を指定すると同じように値を取得できます。成功は
done
を使用し、失敗はfail
を使うことで条件の分岐ができます。今回はこれで終了です。
次回はaxiosで同じ事をしてみます。
- 投稿日:2020-11-27T01:04:07+09:00
switch文[PHP]
switch文をPHPで書く
if, elseifによる分岐が多く複雑な場合、switch文で書き換えるとシンプルで読みやすいコードにできる。switch(式)の(式)がcaseの値と一致したとき、そのブロックが実行される。caseのどれにも一致しなかった時、defaultのブロックが実行される
break
break命令は現在のブロックから脱出するための命令。break命令がないと、後ろに続くcaseブロックが続けて実行されてしまう
switch($remainder) { case 0: echo "大吉です。"; break; case 1: echo "中吉です。"; break; case 2: echo "小吉です。"; break; default: echo "凶です。"; break; }以上!