20200913のlaravelに関する記事は10件です。

[Laravel�] joinで結合先のカラムの取得がものすごく簡単でびっくりした話

User.php
publict function content()
{
   $this->join('content', 'content.user_id', 'user.user_id')
}

とすると、発行されるSQLは

SELECT *
from user
inner join content
on 
content.user_id = user.user_id
where user.deleted_at is NULL

となる。多分(間違っていたらすみません)

ここでcontentの主キ-も(content_id)取得したいとする。
そこでselect()の出番

次のようにする

$this->join('content', 'content.user_id', 'user.user_id')
     ->select('user.*', 'content.content_id')

すると

SELECT user.*,
       content.content_id
from user
inner join content
on 
content.user_id = user.user_id
where user.deleted_at is NULL

簡単に結合先のカラム取得できた。

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

Laravel 8 の新機能!マイグレーションスカッシングで肥大化したマイグレーションファイルを1つにまとめる

大規模または長期間運用されているアプリケーションでは、
数年以上前に作成されたマイグレーションファイルが膨大な数になってきます。

データベーステストを行っているプロジェクトでは、マイグレーションを適用するのに時間がかかりテストの速度が低下する懸念があります。
これの解決策として、Laravel 8ではマイグレーションスカッシング(スキーマダンプ)と呼ばれる新機能が追加されました。

mysqldump まはた pgdump を使用して現在のスキーマの状態からダンプを作成します。
(MySQL、PostgreSQL、SQLiteデータベースのみサポート)

公式ドキュメント・ソース

マイグレーションファイルを確認

初期状態のままですが...下記のマイグレーションファイルがあるとします。

$ ls -1 database/migrations
2014_10_12_000000_create_users_table.php
2014_10_12_100000_create_password_resets_table.php
2019_08_19_000000_create_failed_jobs_table.php

スキーマダンプ コマンド

$ php artisan schema:dump

--prune オプションを付けると、既存のマイグレーションファイルの削除が行われます。

$ php artisan schema:dump --prune

database/schema/mysql-schema.sql が生成されます。
/database/schema/<DB_CONNECTION>-schema.mysql

database/schema/mysql-schema.sql
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `failed_jobs` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `migrations` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `migration` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `batch` int NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `password_resets` (
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `token` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  KEY `password_resets_email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `users` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `email_verified_at` timestamp NULL DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

INSERT INTO `migrations` VALUES (1,'2014_10_12_000000_create_users_table',1);
INSERT INTO `migrations` VALUES (2,'2014_10_12_100000_create_password_resets_table',1);
INSERT INTO `migrations` VALUES (3,'2019_08_19_000000_create_failed_jobs_table',1);

migrations 以外のテーブルのデータはダンプされず、テーブル構成のみダンプされるみたいです。(マイグレーションなのにデータまでダンプされたら困りますが...)

マイグレーションを実行する

$ php artisan migrate:reset
$ php artisan migrate
Loading stored database schema: /Users/ucan-lab/laravel-love/backend/database/schema/mysql-schema.sql
Loaded stored database schema. (128.57ms)
Nothing to migrate.

mysql-schema.sql が読み込まれて、既存のマイグレーションファイルは無視されてます。

スキーマダンプのメリット

前述の通り、データベーステストが高速化されるメリットがあります。

また、スキーマダンプはマイグレーションファイルではなく mysqldump コマンドを使って現在のデータベースからダンプファイルを作成されています。
本来の用途とは違うかもしれませんが、マイグレーションファイルでデータベースが管理されていないプロジェクトからLaravelに移行する際はとても役に立つ機能ではないでしょうか。

使ってみた感想

mysqldump コマンドをLaravelで実行しているので、Dockerでやろうとするとmysqldumpコマンドをコンテナ内で使えるようにしてあげる必要があるし、debianのdefault-mysql-clientはMariaDBに統合されたようでLaravelで実行しているオプションが存在していなかったりとDockerで頑張ろうとすると手間かもしれません...今回はローカルで試してみました。

参考

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

Laravel開発環境構築

Laravel-base

ネットを探すと色々あるのですが、個人的に一番しっくりくるLaravel開発環境です。
忘れないようにメモ的な意味でアップします。
DockerでNginx、PHP-FPM、MySQLを構成しています。
ペースにしているのは下記サイトに記載されていた方法です。ディレクトリやPHPのバージョンが違いますが、ほぼ同じやり方で記載しています。

参考:
DigitalOcean - How To Set Up Laravel, Nginx, and MySQL with Docker Compose
https://www.digitalocean.com/community/tutorials/how-to-set-up-laravel-nginx-and-mysql-with-docker-compose

Githubにもアップしています。
https://github.com/noktone/laravel-base

Dockerのインストール

インストール

以下はUbuntu18.04での作業になります。Macの人はDocker for Macを、Windowsの人はDocker for Windowsをインストールしてください。

# 旧バージョンのDockerをアンインストール
$ sudo apt-get remove docker docker-engine docker.io

# aptのアップデート
$ sudo apt-get update

# HTTPSリポジトリを利用できるようにする
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common

# GPGキーの追加
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# DockerリポジトリをAPTソースに追加
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

# インストール
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-compose

インストール後の処理

一般ユーザーでDockerを実行できるようにする

# ユーザーをdockerグループに追加
$ sudo groupadd docker
$ sudo usermod -aG docker $USER

# テスト
$ docker run hello-world

エラーの一例

下記エラーが出る場合(idコマンドなどでdockerグループが表示されない場合です)があります。

docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/create: dial unix /var/run/docker.sock: connect: permission denied.
See 'docker run --help'.

dockerグループへログインして有効にします。

$ newgrp docker

# プライマリのグループがdockerになるので、ユーザーのグループに戻す(hogeユーザーのhogeグループの場合)
$ newgrp hoge

LaravelのベースプロジェクトをGitから取得する

composerなどで作成してもよいですが、ここではgitから取得する方法で作成します。
また、ホームディレクトリにsrc/laravel-baseというディレクトリを作ることを前提にしています。適宜変更してください。

$ mkdir -p ~/src/laravel-base
$ cd ~/src/laravel-base

# laravleの最新版をclone
$ git clone https://github.com/laravel/laravel.git

# 特定のバージョンを指定する場合は、cloneする際にバージョンを指定します。
$ git clone -v 5.5.28 https://github.com/laravel/laravel.git

Composer

$ cd ~/src/laravel-base/laravel
$ docker run --rm -v $(pwd):/app composer install

docker runコマンドに--vオプションをつけることで現在のディレクトリをバインドマウントしたコンテナが作成されます。Windowsの人は「$(pwd)」のところに直接パスを書きます。--rmはDocker終了後に削除するオプションです。別にこの段階やらなくても大丈夫です。

Linuxの人はアクセス権の問題を回避するため、所有者をroot以外のユーザーに設定します。

$ sudo chown -R $USER:$USER ~/src/laravel-base

Docker Composeファイルの作成

mroongaのDockerイメージはrootパスワードやdatabaseを自動作成しませんが、ここではMySQLのDockerイメージを使用する場合に備えて記載しています。
PHPはDockerfileで指定するため、後ほど作成します。

$ vim docker-compose.yml
version: '3'
services:

  #PHP Service
  app:
    build:
      context: .
      dockerfile: ./php/Dockerfile
    container_name: app
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: app
      SERVICE_TAGS: dev
    working_dir: /var/www
    volumes:
      - ./laravel:/var/www
      - ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
    networks:
      - app-network

  #Nginx Service
  webserver:
    image: nginx:alpine
    container_name: webserver
    restart: unless-stopped
    tty: true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./laravel:/var/www
      - ./nginx/conf.d/:/etc/nginx/conf.d/
    networks:
      - app-network

  #MySQL Service
  db:
    image: mysql:8
    container_name: db
    restart: unless-stopped
    tty: true
    ports:
      - "3306:3306"
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_USER: laravel
      MYSQL_PASSWORD: password
      TZ: 'Asia/Tokyo'
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    volumes:
      - dbdata:/var/lib/mysql/
      - ./mysql/log:/var/log/mysql
      - ./mysql/my.cnf:/etc/mysql/my.cnf
    networks:
      - app-network

#Docker Networks
networks:
  app-network:
    driver: bridge
#Volumes
volumes:
  dbdata:
    driver: local

Dockerfileの作成

PHP7.4-FPM用のDockerfileを作成します。

$ vim ~/src/laravel-base/php/Dockerfile
FROM php:7.4-fpm

# Copy composer.lock and composer.json
COPY ./laravel/composer.lock /var/www/
COPY ./laravel/composer.json /var/www/

# Set working directory
WORKDIR /var/www

# Install dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    libpng-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    git \
    libonig-dev \
    curl \
    libzip-dev \
    zlib1g-dev

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# node.js
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs

# Install extensions
RUN docker-php-ext-install pdo_mysql zip exif pcntl
RUN docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/
RUN docker-php-ext-install gd

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Add user for laravel application
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www

# Copy existing application directory contents
COPY . /var/www

# Copy existing application directory permissions
COPY --chown=www:www . /var/www

# Change current user to www
USER www

# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

PHPの設定

PHP7.2-FPMの設定です。phpディレクトリを作成して、その配下にiniファイルを設置します。

$ mkdir ~/src/laravel-base/php
$ vim ~/src/laravel-base/php/local.ini
upload_max_filesize=40M
post_max_size=40M
date.timezone = "Asia/Tokyo"

Nginxの設定

Nginxのコンフィグファイルを作成します。80版ポートで動作させてます。

$ mkdir -p ~/src/laravel-base/nginx/conf.d
$ vim ~/src/laravel-base/nginx/conf.d/app.conf
server {
    listen 80;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }
}

動作時にタイムアウトが問題になる場合は下記 keepalive_timeout、send_timeout、fastcgi_read_timeout、fastcgi_connect_timeout、fastcgi_send_timeoutオプションを指定する(未検証。増やしすぎるとパフォーマンスの劣化につながるため注意)

server {
    listen 80;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;
    keepalive_timeout 600;
    send_timeout 600;
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_read_timeout 600;
        fastcgi_connect_timeout 600;
        fastcgi_send_timeout 600;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }
}

MySQLの設定

$ mkdir ~/src/laravel-base/mysql
$ vim ~/src/laravel-base/mysql/my.cnf

general_logなどは状況によって。

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_bin
general_log = 0
general_log_file = /var/lib/mysql/general.log
log_timestamps=SYSTEM
secure-file-priv=NULL
default-authentication-plugin = mysql_native_password

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

ログ用のディレクトリも作成しておきます。

$ mkdir ~/src/laravel-base/mysql/log

Laravelの環境設定ファイルの作成

Laravelの環境設定ファイルを作成します。環境設定ファイル(.env)はgitの管理下からは外しますので、本番環境では別途用意します。

$ cp .env.example .env
$ vim .env

laravel-baseをgit cloneした場合は下記を参考にしてください。DB_HOSTにはデータベースのコンテナ名を記載します。

APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

Gitの管理下に含めないファイル

Docker用のファイルなど、アプリケーションに直接関係のないファイルはGitの管理下には置かないようにします。

$ vim .gitignore
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
docker-compose.yml
Dockerfile

パッケージのインストール

下記を実行して、venderパッケージをインストールします。

$ docker-compose exec app php composer install

Dockerコンテナの作成とLaravelの起動時の設定

コンテナの作成と起動を実行します。

$ docker-compose up -d

作成が終わったら、動作しているか確認します。

# 起動中のコンテナの確認
$ docker ps

Laravelのアプリケーションキーを生成します。

$ docker-compose exec app php artisan key:generate

設定をキャッシュする場合は以下を実行します。設定内容が/var/www/bootstrap/cache/config.phpコンテナにロードされます。

$ docker-compose exec app php artisan config:cache

マイグレーションを実行し、データベース無いに認証機能に必要なテーブルを作成

$ docker-compose exec app php artisan migrate

設定が完了したら、下記URLで確認します。
http://localhost
終了する場合は下記を実行します。

$ docker-compose down

# volumeごと消すとき(MySQLがうまく設定できないとか。データが消えるので注意)
docker-compose down --volumes

不調のときに試すもの。データが消えたりするので注意

# 止まってるコンテナ、使われてないボリューム、使われてないネットワーク、使われてないイメージを削除します。
$ docker system prune 
# 個別にやる場合は下記になります。
$ docker image prune
$ docker container prune
$ docker network prune
$ docker volume prune

MySQLのDBとユーザーの作成

docker-composeで定義しているのですでに作成されているはずですが、やり方を忘れないように記載

$ mysql -u root -p --protocol=tcp
$ CREATE DATABASE laravel;
$ CREATE USER 'laravel'@'%' IDENTIFIED BY 'password';
$ GRANT ALL ON laravel.* TO 'laravel'@'%' WITH GRANT OPTION;
$ FLUSH PRIVILEGES;
$ exit
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelでサブドメイン対応(さくらレンタルサーバ)

やりたいこと

main.hoge.hoge → routes/main.phpでルーティング
api.hoge.hoge → routes/api.phpでルーティング
test.hoge.hoge → routes/test.phpでルーティング

①テスト環境と本番環境でドメインを分けるためにconfig設定

configフォルダ内にmyapp.php作成
\Config::get('myapp.domain.main')でアクセスできる

config/myapp.php
<?php
return [
    'domain' => [
        'main' => 'main.hoge.hoge',
        'api' => 'api.hoge.hoge',
        'test' => 'test.hoge.hoge',
    ]
];

コンフィグファイルのキャッシュクリアをしないと反映されない。

$ php artisan config:cache

②ルーティング設定

/app/Providers/RouteServiceProvider.php
class RouteServiceProvider extends ServiceProvider
{

    public function map()
    {
        $this->mapApiRoutes();

        //$this->mapWebRoutes();
        $this->mapMainRoutes();
        $this->mapTestRoutes();
    }

    protected function mapMainRoutes()
    {
        Route::domain(\Config::get('myapp.domain.main'))
             ->middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/main.php'));
    }
    protected function mapTestRoutes()
    {
        Route::domain(\Config::get('myapp.domain.test'))
             ->middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/test.php'));
    }
    protected function mapApiRoutes()
    {
        Route::domain(\Config::get('api.domain.test'))
             ->middleware('api')
             ->namespace($this->namespace)
             ->group(base_path('routes/api.php'));
    }

routes/main.php,routes/test.phpを作ってルーティングさせる。

おわり。

CROS対応

サブドメインで分けるとaxiosからの通信がクロスドメインで引っかかるので設定
※バージョン指定しないとエラーになります。

$ composer require barryvdh/laravel-cors:^0.11

続きは下記参照
Laravelでクロスオリジン(CORS)に対応する為のメモです

※crosの設定を上に持ってこないと一部ヘッダが付与されない場合がある?

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'cros',
            'throttle:60,1',
            'bindings',

        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'cros' => \Barryvdh\Cors\HandleCors::class,
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];

セッション共有

.envにSESSION_DOMAIN設定すればサブドメイン間でセッション共有可能

'''.enb
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_DOMAIN='.hoge.hoge'
'''

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

EC2・RDSでLaravelの環境構築【PHP・MySQL・Nginx】

実現したいこと

既存で作成したLaravelのWebアプリケーションをEC2・RDSの環境にデプロイすることです。

前提

  • GitHubに自身で作成したLaravelのWebアプリケーションがリポジトリに上がっている
  • Laravel6.1
  • PHP7.3
  • MySQL5.7.31
  • Nginx1.17.10

EC2

EC2の作成

ステップ 1: Amazon マシンイメージ (AMI)

スクリーンショット 2020-09-07 7.52.19.png

  • Amazon Linux 2 AMIを選択

ステップ 2: インスタンスタイプの選択

スクリーンショット 2020-09-09 8.12.51.png

  • t2.micro

ステップ 3: インスタンスの詳細の設定

スクリーンショット 2020-09-07 7.54.53.png

  • 全てデフォルト

ステップ 4: ストレージの追加

スクリーンショット 2020-09-07 7.55.31.png

  • 全てデフォルト

ステップ 5: タグの追加

スクリーンショット 2020-09-07 7.56.11.png

  • キー Name
  • 値 適宜好きな名前

ステップ 6: セキュリティグループの設定

スクリーンショット 2020-09-07 7.57.29.png

  • ルールの追加
    • SSH *アクセス元を絞るため、ソースをマイIPを選択しましょう。
    • HTTP

ステップ 7: インスタンス作成の確認

スクリーンショット 2020-09-09 8.21.19.png

  • 起動

スクリーンショット 2020-09-09 8.22.48.png

  • キーペアの作成 *すでに作っているなら既存のキーペアを使用しましょう。ECSにSSHでログインする際に必要になります

SSHで接続

スクリーンショット 2020-09-09 8.34.14.png

  1. キーペアファイルを配置
  2. アクセス権を400に設定
  3. 接続 ssh -i ~/.ssh/[SHHキー.pem] ec2-user@[ドメイン名かIPアドレス(パブリック DNS (IPv4))]

■参考記事
MacのターミナルでEC2にSSHでログインする

Nginxのインストール

yum update

$ sudo yum update -y

Nginxをインストール
*適宜、ローカルのバージョンに合わせてインストールしてください。

$ sudo amazon-linux-extras install nginx1.12 -y

Nginxを起動

$ sudo systemctl start nginx

IPv4パブリックIPをブラウザに入力すると下記の画面が表示されます。

スクリーンショット 2020-09-09 8.59.03.png

PHPのインストール

PHPのインストール
*適宜、ローカルのバージョンに合わせてインストールしてください。

$ sudo amazon-linux-extras install php7.3

下記のコマンドでインストールされているか確認

$ php -v

Nginxを使用するため、PHPの設定情報を変更

$ sudo vi /etc/php-fpm.d/www.conf
/etc/php-fpm.d/www.conf
# 略
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
; RPM: apache user chosen to provide access to the same directories as httpd
user = nginx # デフォルトは「user = apache」
; RPM: Keep a group allowed to write in log dir.
group = nginx # デフォルトは「user = apache」
# 略

Nginxを再起動

$ sudo systemctl restart nginx

■参考記事
AWS EC2 + nginx + Laravel + RDS(PostgreSQL)の環境を構築する

Laraveのリポジトリをclone

Composer

Composerをインストール

$ cd ~
$ sudo curl -sS https://getcomposer.org/installer | php # コンポーザーのインストール
$ sudo chown root:root composer.phar 
$ sudo mv composer.phar /usr/bin/composer # パスを通す
$ composer # インストールされたか確認

php-mbstringとphp-xmlをインストール

$ sudo yum install php-mbstring php-xml -y

Git

Gitのインストール

$ sudo yum install git # インストール
$ git --version # インストールされたかバージョン確認

GitHubのリポジトリからclone

ディレクトリ作成

$ sudo mkdir /var/www
$ cd /var/www

clone
*今回はHTTPS方式でクローンしましたが、もちろんSSH方式でも可能です。

$ sudo git clone https://github.com/[ユーザー名]/[リポトリ名].git

Nginxの設定情報の変更

$ sudo vi /etc/nginx/nginx.conf
/etc/nginx/nginx.conf
# 略
http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /var/www/リポジトリ名/public; # ここを修正

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            try_files $uri $uri/ /index.php$is_args$args; # ここを修正
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl http2 default_server;
#        listen       [::]:443 ssl http2 default_server;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers HIGH:!aNULL:!MD5;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        location / {
#        }
#
#        error_page 404 /404.html;
#            location = /40x.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#            location = /50x.html {
#        }
#    }

}
# 略

Nginxを再起動

$ sudo systemctl restart nginx

各種インストール、設定

$ cd /var/www/リポジトリ名
$ sudo composer install # composerのインストール
$ sudo chmod -R 777 storage/ # 権限変更

.envファイルの作成

$ sudo touch .env 

.envの情報を適宜変更してください。DBの設定はRDSの設定後に行います。

/var/www/リポジトリ名/.env
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"

これでIPv4パブリックIPをブラウザに入力すればLaravelの画面が表示されると思います。

■参考記事
Laravelのプロジェクトをgit cloneしたときの手順

RDS

設定情報の値は適宜情報を入力し、作成を行ってください。

参考までに私は下記の画像の値を入力しました。

スクリーンショット 2020-09-13 12.01.11.png

スクリーンショット 2020-09-13 12.01.30.png

スクリーンショット 2020-09-13 12.02.08.png

スクリーンショット 2020-09-13 12.02.34.png

スクリーンショット 2020-09-13 12.03.14.png

Virtual Private Cloud (VPC)、サブネットグループ、既存のVPCセキュリティグループはEC2の設定に合わせました。
最初のデータベース名の値は.envファイルを設定する際に使用します。

セキリティグループ

今回のRDSのセキリティグループの設定はEC2と同一のものにしました。

セキリティグループのインバウンドルールの設定は、「HTTP」「SSH」を既存で追加していますので、ここで新たに「MYSQL/Auora」を追加しましょう。

スクリーンショット 2020-09-13 12.42.28.png

.envの設定、マイグーレション

.envの設定に先ほどのRDSの設定した値を入力します。

$ sudo vi .env
/var/www/リポジトリ名/.env
DB_CONNECTION=mysql
DB_HOST=エンドポイント
DB_PORT=3306
DB_DATABASE=最初のデータベース名
DB_USERNAME=マスターユーザー名
DB_PASSWORD=マスターパスワード

私は誤ってDB_DATABASEの値をDBクラスター識別子で設定してかなりハマりました。

最後にマイグレーションを実行

$ sudo php artisan migrate

これでIPv4パブリックIPをブラウザに入力するとデータベースが繋がった状態でLaravelの画面が表示されます。

全体像の参考

ゼロからわかるAmazon Web Services超入門 はじめてのクラウド

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

react + laravelで予約機能付きHPを作成する

おおまかな流れ

laravelでreactを使えるようにする

ターミナルで以下のコマンド群を実行する

まずは、laravelのuiパッケージをインストールする
composer require laravel/ui

次に、reactを使えるようにする
php artisan ui react

最後にnpmをインストールし、自動的にファイルの変更を検知してコンパイルをしてもらう
npm install && npm run watch-poll

reactで開発できるようにするため、laravelのファイル群を変更する

一番最初にリクエストを投げた時にレスポンスで返ってくるbladeファイルを作成する。
下記のコードにある<div id="index"></div>の部分に注目する。
ここ部分にreactで作成する箇所が埋め込まれるイメージ。

top.blade.php
<body>
    <div id="index"></div>
</body>

次は、下記のコードにある
if (document.getElementById('index')) {
ReactDOM.render(<Index />, document.getElementById('index'));
}

の部分に注目する。
先ほど設定したid・indexの部分にこのIndexコンポーネントを埋め込む

Index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Top from './Top';
import Greetings from './Greetings';
import Profile from './Profile';
import Fee from './Fee';
import Contact from './Contact';
import Access from './Access';
import Reservation from './Reservation';
import '../../../public/css/index.css';

export default class Index extends Component {
    render() {
        return (
            <div>
                    <BrowserRouter>
                        <Switch>
                            <Route exact path="/" component={Top} />
                            <Route exact path="/greetings" component={Greetings} />
                            <Route exact path="/profile" component={Profile} />
                            <Route exact path="/reservation" component={Reservation} />
                            <Route exact path="/fee" component={Fee} />
                            <Route exact path="/contact" component={Contact} />
                            <Route exact path="/access" component={Access} />
                        </Switch>
                    </BrowserRouter>
            </div>
        );
    }
}

if (document.getElementById('index')) {
    ReactDOM.render(<Index />, document.getElementById('index'));
}

SPAを作成するためにreact-router-domをインストールして設定する

ターミナルでnpm install react-router-domを実行すると、package.jsonにインストールしたモジュールが記述される。
これでモジュールを使用する準備が整った。

まずは、BrowserRouter、Route、Switchモジュールをimportする。
次に、BrowserRouter、Route、Switchの順に入れ子にする。

<Route path="/" component={Top} />の解説をする。
/にアクセスがあった場合に、Topコンポーネントを表示させるということである。

ここに遷移させるためのアンカー部分は、Linkモジュールを使用する。下記に例を示す。
Linkモジュールはレンダーされるとaタグになる。このLinkモジュールで生成したアンカーをクリックすると、
上記で設定した<Route path="/" component={Top} />の通り、Topコンポーネントが表示される。

ルーティングはこれで完成。

Header.js
<Link to="/"</Link>
Index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Top from './Top';

export default class Index extends Component {
    render() {
        return (
            <div>
                    <BrowserRouter>
                        <Switch>
                            <Route path="/" component={Top} />
                        </Switch>
                    </BrowserRouter>
            </div>
        );
    }
}

予約機能の作成

コードが長くてみにくいので必要な部分をピックアップして解説していく。

下記のコードにあるconstructor内では、stateを定義したり関数をバインドする。
これをしないとstate・関数が使えない。

constructor(props) {
super(props)
this.state = {
date: "",
}
this.onDateChange = this.onDateChange.bind(this);
}

下記のコードは、stateであるdateにフォームで入力された値を格納している。
onDateChange(e) {
this.setState({ date: e.target.value })
}

下記のコードのdisabled={this.state.isDisabled}はisDisabledのstateがtrueの場合はsubmitできないようにしている。
onClick={this.postReservation}は、submitした際にpostReervationメソッドが実行される。
postReservationメソッドについての説明はこの後に行う。
<Button className="float-right" id="btn" variant="contained" disabled={this.state.isDisabled} onClick={this.postReservation} color="primary">
送信する
</Button>

下記のコードの解説をする。
変数dataにフィールドに入力された値を格納する。
axiosを使用して非同期通信を行なっている。post送信で/reservationに対して先ほど定義した変数dataを送っている。
では。送信した先の/reservationを見ていく。

    const data = {
            date: this.state.date,
        }

            axios.post('/reservation', data)

先ほど解説した通りに非同期通信を行うと下記の処理が実行される。
先ほど定数dataで送った値が$requestに格納されている。
ReservationSendmailクラスをインスタンス化する際に$date渡す。

ReservationController.php
<?php

namespace App\Http\Controllers;

use App\Mail\ReservationSendmail;
use Illuminate\Support\Facades\Mail;
use App\Http\Requests\ReservationRequest;

class ReservationController extends Controller
{
    public function store(ReservationRequest $request)
    {
        $date = $request->date;
        $to = 'test@gmail.com';
        Mail::to($to)->send(new ReservationSendmail($date);

        return;
    }
}

次はReservationSendmailクラスをみる。

ReservationSendmail.php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class ReservationSendmail extends Mailable
{
    use Queueable, SerializesModels;

    private $date;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($date)
    {
        $this->date = $date; //ReservationControllerから送られてきた$dateをプロパティである$this->dateに格納している。
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this
            ->from('tatataabcd@gmail.com') //送信元のメールアドレス
            ->subject('自動送信メール') //メールのタイトル
            ->view('reservation.mail') //viewはreservation.mail.blade.phpを使用
            ->with([
                'date' => $this->date,  //先ほど格納したプロパティをreservation.mail.blade.phpでも使えるようにしている
            ]);
    }
}

次にreservation.mail.blade.phpを見る

mail.blade.php
お問い合わせ内容を受け付けました。<br>
<br>
■日時<br>
{!! $date !!}<br> この内容がReservationControllerのtoに指定したメールアドレスに送信される。
Reservation.js
import React, { Component } from 'react';
import moment from 'moment';
import Header from './Header';
import { Button } from '@material-ui/core';
import '../../../public/css/reservation.css';

class Reservation extends Component {
    constructor(props) {
        super(props)
        this.state = {
            date: "",
            name: "",
            phone: "",
            email: "",
            age: "",
            state: "",
            gender: "",
            isDisabled: false,
            errors: {
                date: [],
                name: [],
                phone: [],
                email: [],
                age: [],
                state: [],
                gender: [],
            }
        }
        this.onDateChange = this.onDateChange.bind(this);
        this.postReservation = this.postReservation.bind(this);
    }

    onDateChange(e) {
        this.setState({ date: e.target.value })
    }

    postReservation(e) {
        if (this.state.date !== "" && this.state.name !== "" && this.state.phone !== "" && this.state.email) {
            this.setState({
                isDisabled: true
            });
        }

        axios
            .post('/reservation', data)
            .then(response => {
                alert('予約を受け付けました。')
                this.setState({
                    isDisabled: false
                });

                this.setState({
                    errors: []
                });
            })
            .catch(error => {
                this.setState({
                    isDisabled: false
                });
                console.log(error.response.data.errors);
                const errors = this.state.errors;

                // 次の日から
                const nextDay = moment().add('1', 'd').format('YYYY-MM-DD');
                let reservationDay = this.state.date;
                reservationDay = reservationDay.slice(-16, -6);

                // 水木のみ
                const date = moment(this.state.date);
                const dayOfWeek = date.day();

                // 時間指定
                const hour = date.hour();
                const minute = date.minute();
                const hourAndMinute = hour + ':' + minute;

                if (this.state.date === "" ||
                    nextDay > reservationDay ||
                    dayOfWeek !== 3 &&
                    dayOfWeek !== 4 ||
                    hourAndMinute !== 10 + ':' + 0 &&
                    hourAndMinute !== 11 + ':' + 0 &&
                    hourAndMinute !== 13 + ':' + 0 &&
                    hourAndMinute !== 14 + ':' + 0 &&
                    hourAndMinute !== 15 + ':' + 0 &&
                    hourAndMinute !== 15 + ':' + 30 &&
                    hourAndMinute !== 16 + ':' + 0 &&
                    hourAndMinute !== 16 + ':' + 30
                ) {
                    errors.date = error.response.data.errors.date[0];
                } else {
                    errors.date = "";
                }
                this.setState({
                    errors: errors
                });

                if (this.state.name === "") {
                    errors.name = error.response.data.errors.name[0];
                } else {
                    errors.name = "";
                }
                this.setState({
                    errors: errors
                });

                if (this.state.phone === "" || isNaN(this.state.phone)) {
                    errors.phone = error.response.data.errors.phone[0];
                } else {
                    errors.phone = "";
                }
                this.setState({
                    errors: errors
                });

                if (this.state.email == "" || this.validateEmail(this.state.email)) {
                    errors.email = error.response.data.errors.email[0];
                } else {
                    errors.email = "";
                }
                this.setState({
                    errors: errors
                });
            });
    }

    render() {
        return (
            <React.Fragment>
                <Header />
                <div className="container">
                    <div className="row">
                        <div className="col-12">
                            <h1 class="h3 mb-5 mt-5 text-center">予約フォーム</h1>
                            <div className="form-group">
                                <span>予約日時</span>
                                <input className="form-control" type="datetime-local" name="date"
                                    value={this.state.date} onChange={this.onDateChange} />
                                <p className="err-msg">{this.state.errors.date}</p>
                            </div>


                            <Button className="float-right" id="btn" variant="contained" disabled={this.state.isDisabled} onClick={this.postReservation} color="primary">
                                送信する
                            </Button>
                        </div>
                    </div>
                </div>
            </React.Fragment >
        );
    }
}

export default Reservation;

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

laravel + reactで予約機能付きSPAのHPを作成する

おおまかな流れ

1、laravelでreactが使えるようにターミナルから必要なものをインストールする。
2、ルーティングを設定する
3、予約機能の作成

laravelでreactを使えるようにする

ターミナルで以下のコマンド群を実行する

まずは、laravelのuiパッケージをインストールする
composer require laravel/ui

次に、reactを使えるようにする
php artisan ui react

最後にnpmをインストールし、自動的にファイルの変更を検知してコンパイルをしてもらう
npm install && npm run watch-poll

reactで開発できるようにするため、laravelのファイル群を変更する

一番最初にリクエストを投げた時にレスポンスで返ってくるbladeファイルを作成する。
下記のコードにある<div id="index"></div>の部分に注目する。
ここ部分にreactで作成する箇所が埋め込まれるイメージ。

top.blade.php
<body>
    <div id="index"></div>
</body>

次は、下記のコードにある
if (document.getElementById('index')) {
ReactDOM.render(<Index />, document.getElementById('index'));
}

の部分に注目する。
先ほど設定したid・indexの部分にこのIndexコンポーネントを埋め込む

Index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Top from './Top';
import Greetings from './Greetings';
import Profile from './Profile';
import Fee from './Fee';
import Contact from './Contact';
import Access from './Access';
import Reservation from './Reservation';
import '../../../public/css/index.css';

export default class Index extends Component {
    render() {
        return (
            <div>
                    <BrowserRouter>
                        <Switch>
                            <Route exact path="/" component={Top} />
                            <Route exact path="/greetings" component={Greetings} />
                            <Route exact path="/profile" component={Profile} />
                            <Route exact path="/reservation" component={Reservation} />
                            <Route exact path="/fee" component={Fee} />
                            <Route exact path="/contact" component={Contact} />
                            <Route exact path="/access" component={Access} />
                        </Switch>
                    </BrowserRouter>
            </div>
        );
    }
}

if (document.getElementById('index')) {
    ReactDOM.render(<Index />, document.getElementById('index'));
}

SPAを作成するためにreact-router-domをインストールして設定する

ターミナルでnpm install react-router-domを実行すると、package.jsonにインストールしたモジュールが記述される。
これでモジュールを使用する準備が整った。

まずは、BrowserRouter、Route、Switchモジュールをimportする。
次に、BrowserRouter、Route、Switchの順に入れ子にする。

<Route path="/" component={Top} />の解説をする。
/にアクセスがあった場合に、Topコンポーネントを表示させるということである。

ここに遷移させるためのアンカー部分は、Linkモジュールを使用する。下記に例を示す。
Linkモジュールはレンダーされるとaタグになる。このLinkモジュールで生成したアンカーをクリックすると、
上記で設定した<Route path="/" component={Top} />の通り、Topコンポーネントが表示される。

ルーティングはこれで完成。

Header.js
<Link to="/"</Link>
Index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Top from './Top';

export default class Index extends Component {
    render() {
        return (
            <div>
                    <BrowserRouter>
                        <Switch>
                            <Route path="/" component={Top} />
                        </Switch>
                    </BrowserRouter>
            </div>
        );
    }
}

予約機能の作成

コードが長くてみにくいので必要な部分をピックアップして解説していく。

下記のコードにあるconstructor内では、stateを定義したり関数をバインドする。
これをしないとstate・関数が使えない。

constructor(props) {
super(props)
this.state = {
date: "",
}
this.onDateChange = this.onDateChange.bind(this);
}

下記のコードは、stateであるdateにフォームで入力された値を格納している。
onDateChange(e) {
this.setState({ date: e.target.value })
}

下記のコードのdisabled={this.state.isDisabled}はisDisabledのstateがtrueの場合はsubmitできないようにしている。
onClick={this.postReservation}は、submitした際にpostReervationメソッドが実行される。
postReservationメソッドについての説明はこの後に行う。
<Button className="float-right" id="btn" variant="contained" disabled={this.state.isDisabled} onClick={this.postReservation} color="primary">
送信する
</Button>

下記のコードの解説をする。
定数dataにフィールドに入力された値を格納する。
axiosを使用して非同期通信を行なっている。post送信で/reservationに対して先ほど定義した定数dataを送っている。
では。送信した先の/reservationを見ていく。

    const data = {
            date: this.state.date,
        }

            axios.post('/reservation', data)

先ほど解説した通りに非同期通信を行うと下記の処理が実行される。
先ほど定数dataで送った値が$requestに格納されている。
ReservationSendmailクラスをインスタンス化する際に$date渡す。

ReservationController.php
<?php

namespace App\Http\Controllers;

use App\Mail\ReservationSendmail;
use Illuminate\Support\Facades\Mail;
use App\Http\Requests\ReservationRequest;

class ReservationController extends Controller
{
    public function store(ReservationRequest $request)
    {
        $date = $request->date;
        $to = 'test@gmail.com';
        Mail::to($to)->send(new ReservationSendmail($date);

        return;
    }
}

次はReservationSendmailクラスをみる。

ReservationSendmail.php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class ReservationSendmail extends Mailable
{
    use Queueable, SerializesModels;

    private $date;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($date)
    {
        $this->date = $date; //ReservationControllerから送られてきた$dateをプロパティである$this->dateに格納している。
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this
            ->from('tatataabcd@gmail.com') //送信元のメールアドレス
            ->subject('自動送信メール') //メールのタイトル
            ->view('reservation.mail') //viewはreservation.mail.blade.phpを使用
            ->with([
                'date' => $this->date,  //先ほど格納したプロパティをreservation.mail.blade.phpでも使えるようにしている
            ]);
    }
}

次にreservation.mail.blade.phpを見る

mail.blade.php
お問い合わせ内容を受け付けました。<br>
<br>
■日時<br>
{!! $date !!}<br> この内容がReservationControllerのtoに指定したメールアドレスに送信される。
Reservation.js
import React, { Component } from 'react';
import moment from 'moment';
import Header from './Header';
import { Button } from '@material-ui/core';
import '../../../public/css/reservation.css';

class Reservation extends Component {
    constructor(props) {
        super(props)
        this.state = {
            date: "",
            name: "",
            phone: "",
            email: "",
            age: "",
            state: "",
            gender: "",
            isDisabled: false,
            errors: {
                date: [],
                name: [],
                phone: [],
                email: [],
                age: [],
                state: [],
                gender: [],
            }
        }
        this.onDateChange = this.onDateChange.bind(this);
        this.postReservation = this.postReservation.bind(this);
    }

    onDateChange(e) {
        this.setState({ date: e.target.value })
    }

    postReservation(e) {
        if (this.state.date !== "" && this.state.name !== "" && this.state.phone !== "" && this.state.email) {
            this.setState({
                isDisabled: true
            });
        }

        axios
            .post('/reservation', data)
            .then(response => {
                alert('予約を受け付けました。')
                this.setState({
                    isDisabled: false
                });

                this.setState({
                    errors: []
                });
            })
            .catch(error => {
                this.setState({
                    isDisabled: false
                });
                console.log(error.response.data.errors);
                const errors = this.state.errors;

                // 次の日から
                const nextDay = moment().add('1', 'd').format('YYYY-MM-DD');
                let reservationDay = this.state.date;
                reservationDay = reservationDay.slice(-16, -6);

                // 水木のみ
                const date = moment(this.state.date);
                const dayOfWeek = date.day();

                // 時間指定
                const hour = date.hour();
                const minute = date.minute();
                const hourAndMinute = hour + ':' + minute;

                if (this.state.date === "" ||
                    nextDay > reservationDay ||
                    dayOfWeek !== 3 &&
                    dayOfWeek !== 4 ||
                    hourAndMinute !== 10 + ':' + 0 &&
                    hourAndMinute !== 11 + ':' + 0 &&
                    hourAndMinute !== 13 + ':' + 0 &&
                    hourAndMinute !== 14 + ':' + 0 &&
                    hourAndMinute !== 15 + ':' + 0 &&
                    hourAndMinute !== 15 + ':' + 30 &&
                    hourAndMinute !== 16 + ':' + 0 &&
                    hourAndMinute !== 16 + ':' + 30
                ) {
                    errors.date = error.response.data.errors.date[0];
                } else {
                    errors.date = "";
                }
                this.setState({
                    errors: errors
                });

                if (this.state.name === "") {
                    errors.name = error.response.data.errors.name[0];
                } else {
                    errors.name = "";
                }
                this.setState({
                    errors: errors
                });

                if (this.state.phone === "" || isNaN(this.state.phone)) {
                    errors.phone = error.response.data.errors.phone[0];
                } else {
                    errors.phone = "";
                }
                this.setState({
                    errors: errors
                });

                if (this.state.email == "" || this.validateEmail(this.state.email)) {
                    errors.email = error.response.data.errors.email[0];
                } else {
                    errors.email = "";
                }
                this.setState({
                    errors: errors
                });
            });
    }

    render() {
        return (
            <React.Fragment>
                <Header />
                <div className="container">
                    <div className="row">
                        <div className="col-12">
                            <h1 class="h3 mb-5 mt-5 text-center">予約フォーム</h1>
                            <div className="form-group">
                                <span>予約日時</span>
                                <input className="form-control" type="datetime-local" name="date"
                                    value={this.state.date} onChange={this.onDateChange} />
                                <p className="err-msg">{this.state.errors.date}</p>
                            </div>


                            <Button className="float-right" id="btn" variant="contained" disabled={this.state.isDisabled} onClick={this.postReservation} color="primary">
                                送信する
                            </Button>
                        </div>
                    </div>
                </div>
            </React.Fragment >
        );
    }
}

export default Reservation;

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

Laravel 8 ルーティングで「Target class [コントローラ名] does not exist」がでたときの対処法

Laravel 8 をインストールして、とりあえずCRUDシステム作って8系がどんな感覚か確かめようとして、すぐにつまづいた…(; ・`д・´)
エラーがでたのはルーティングが正しく記述されていなかったのが理由でした。

注意すべきルーティングの設定

Laravel8のルーティングは今までのルーティングと記述の仕方が少しことなります。https://readouble.com/laravel/8.x/ja/routing.html

Laravel8のルーティングを定義する場合、

web.php
Route::resource('/blogs', BlogController::class);

こんな感じで記述しています。

ここで注意しなければならないのが、ルート定義ファイル web.php の冒頭にルート定義で使用されているコントローラの名前空間をuseキーワードを使用して書くということです。

上記の例でいえば

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BlogController;

Route::resource('/blogs', BlogController::class);

BlogControllerクラスを使用しているならば、use App\Http\Controllers\BlogController;と記述してインポートしてください。これを書き忘れると、「Target class [コントローラ名] does not exist」とエラーがでてしまいます。

上記ではリソースフルなルート設定をしましたが、普通にルーティングする場合は、

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BlogController;

Route::get('/blogs', [BlogController::class, 'index']);

第2引数のところは配列になっていますのでご注意ください。もちろん、この記述でもuse App\Http\Controllers\BlogController;を忘れずに!

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

Laravel8を試したら即効でエラー「Target class [〇〇〇Controller] does not exist.」が表示された

はじめに

最近になってLaravel8がリリースされたと聞いて、とりあえず動かしてみようと思ったら開始早々にエラーが発生しました。
laravel8エラー.png
web.phpPostControllerを呼び出す処理を書いたのに上記エラーが発生。Laravel6ではちゃんと呼び出せたのに:joy:
そのときのweb.phpはこちら。

web.php
<?php
use Illuminate\Support\Facades\Route;
Route::get('/', 'PostController@index');

原因調査したら、どうやらLaravel8の変更点の一つであるRouteServiceProvider.phpファイルからデフォルトの名前空間を削除」したのが大きく関係していると判明!Laravel7.xのプロジェクトからLaravel8.xに移行した場合は、この変更点の影響は受けない。Laravel8で作成した新規プロジェクトは変更点を考慮する必要があるそうです。

RouteServiceProvider.phpの変化

Laravel7までは、RouteServiceProvider.phpには次のコードが含まれていました。

RouteServiceProvider.php
protected $namespace = 'App\Http\Controllers';

Route::middleware('web')
      ->namespace($this->namespace)
      ->group(base_path('routes/web.php'));

webミドルウェアとApp\Http\Controllersの名前空間を使用して、routes/web.phpにルートをロードするよう指示しています。

web.php
// Laravelは、App\Http\Controllers\PostControllerを検索します
Route::get('/', 'PostController@index');

Laravel8では、$namespace変数が削除され、Route宣言が次のように変更されました。

RouteServiceProvider.php
Route::middleware('web')
      ->group(base_path('routes/web.php'));

App\Http\Controllersの名前空間を使用していないです。

web.php
// Laravelは、App\Http\Controllers内でコントローラーを検索しません
Route::get('/', 'PostController@index');

このあと、どう修正すればよいのか解決方法を3つご紹介します。

解決方法

  • RouteServiceProvider.phpで名前空間を手動で追加する
  • web.phpで完全な名前空間を使用する
  • web.phpでアクション構文を使用する

RouteServiceProvider.phpで名前空間を手動で追加する

Laravel7.x以前と同じ方法です。

RouteServiceProvider.php
protected $namespace = 'App\Http\Controllers'; //追加
public function boot()
    {
        $this->configureRateLimiting();

        $this->routes(function () {
            Route::middleware('web')
                ->namespace($this->namespace) //追加
                ->group(base_path('routes/web.php'));

            Route::prefix('api')
                ->middleware('api')
                ->namespace($this->namespace) //追加
                ->group(base_path('routes/api.php'));
        });
    }

web.phpで完全な名前空間を使用する

コントローラーの名前の前に名前空間を追加します。
例として、app/Http/Controllersフォルダ内のPostControllerを呼び出したいときは、次の通り記載します。

web.php
Route::get('/', 'App\Http\Controllers\PostController@index');

web.phpでアクション構文を使用する

web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
Route::get('/', [PostController::class, 'index']);

配列で使用したいクラスとメソッドを指定します。
具体的に配列内の値は
PostsController :: class -> App \ Http \ Controllers \ PostsControllerを返している
index -> PostController.phpのindexメソッドを呼び出している

終わりに

Laravel開発で初めて遭遇したエラーでしたので勉強になりました。
ただ、Laravel8の新機能・変更点を理解してから使用してみようと思いました。理解せず使用すると痛い目に遭いますね:joy:

参考

How to fix ‘Target class does not exist’ in Laravel 8

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

Laravelでneo4jを使う④マイグレーション

事前準備

・app/database/labelsフォルダの作成

$ php artisan neo4j:make:migration create_race_label

$ php artisan neo4j:migrate

コマンド  説明
$label->unique('email') プロパティに一意の制約を追加する
$label->dropUnique('email') プロパティから一意の制約を削除する
$label->index('uuid') プロパティにインデックスを追加する
$label->dropIndex('uuid') プロパティからインデックスを削除しています

コマンド 説明

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