20200709のlaravelに関する記事は7件です。

(Laravel)変更はマッピングに追加するだけのSingleton登録サービスプロバイダの実装

希望:複数クラスで依存されるクラスのSingleton化を楽に管理したい

複数のクラスで依存されるSingletonにしても問題がいないクラスだけを、ServiceProviderで同じインスタンスが取得されるよう実装しようと思いました。
数が多かったのでマッピングを編集すればSingletonに登録するクラスが追加できるように実装してみました。

app/Providers/SingletonServiceClassRegisterProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class SingletonServiceClassRegisterProvider extends ServiceProvider
{
    protected function singletonTargetServiceClassNamesMap():array
    {
        return [
            \App\Service\PlanService::class,
        ];
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->registerAllClassInstanceInClassNamesMap($this->singletonTargetServiceClassNamesMap());
    }

    protected function registerAllClassInstanceInClassNamesMap(array $classNamesMap):void
    {
        foreach($classNamesMap as $key=>$className){
            $instance = $this->app->make($className);
            $this->app->instance(
                is_numeric($key) ? $className : $key,
                $instance
            );
        }
    }

}

これで、マッピングにSignletonで登録したいクラスを追加、外したいクラスを削除することで管理できるサービスプロバイダになりました。
以上です。

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

【Laravel】Bladeの構文

書籍のアウトプットとして

Bladeの構文

レイアウト内でヒョ持したり、レイアウトを継承して複数を組み合わせたりするための構文について。

値の表示

{{変数関数等}}

{{}}はHTMLスケープ処理されるためHTMLタグとしては機能しない。
エスケープ処理してほしくない場合は以下のようにする。

{{!! 変数関数等 !!}}

@ifディレクティブ

Bladeにはディレクティブという言語で言う構文が用意されている。
まずは条件分岐(if)に相当するディレクティブから。

条件がtrueの時に表示する。

@if (条件)
...出力内容...
@endif

条件によって異なる表示をする

@if (条件)
...出力内容...
@else
...出力内容...
@endif

複数の条件を設定

@if (条件)
...出力内容...
@endif(条件) 
...出力内容...
@else
...出力内容...
@endif(条件) 

@ifには@else@elseifが用意されている

ディレクティブは何かを実行するのではなく表示する
条件がこれなら表示する、表示しないという動きをする。

@ifを使ってみる

以下のように修正する

index.blade.php
  <body>
    <h1>Blade/Index</h1>
    @if ($msg!='')
    <p>こんにちは、{{$msg}}さん。</p>
    @else
    <p>なにか書いてください</p>
    @endif
    <form action="/hello" method="post">
      @csrf
      <input type="text" name="msg">
      <input type="submit">
    </form>
  </body>

次はコントローラを修正

    public function index()
    {
        return view('hello.index', ['msg'=>'']);
    }
    public function post(Request $request)
    {
      return view('hello.index', ['msg'=>$request->msg]);
    }

/helloにアクセスして正常に表示されているか確認する。

特殊なディレクティブ

条件が非成立のときに表示

@unless(条件)
...表示内容...
@endunless

条件がfalseの時に表示をする。

変数が空の場合に表示

@empty(変数)
...表示内容...
@endempty

変数が定義済みの場合に表示

@isset(変数)
...表示内容...
@endisset

emptyと似ているが、これは変数そのものが定義されているかどうかを判定する。

@issetで変数定義をチェックする

@issetを使って例を見てみる。

index.blade.php
  <body>
    <h1>Blade/Index</h1>
    @isset ($msg)
    <p>こんにちは、{{$msg}}さん。</p>
    @else
    <p>なにか書いてください</p>
    @endisset
    <form action="/hello" method="post">
      @csrf
      <input type="text" name="msg">
      <input type="submit">
    </form>
  </body>

コントローラはこのように

    public function index()
    {
        return view('hello.index');
    }
    public function post(Request $request)
    {
      return view('hello.index', ['msg'=>$request->msg]);
    }

indexアクションメソッドでは値をテンプレに渡していない。
そのため、テンプレ側では$msgは未定義になる。

繰り返しのディレクティブ

forに相当

@for(初期化;条件;後処理)
...繰り返す表示...
#endfor

foreachに相当

@foreach($配列 as $変数)
...繰り返す表示...
@endforeach

foreach-elseに相当

@forelse($配列 as $変数)
...繰り返す表示...
@empty
...$変数が空のときの表示...
@endforelse

foreachにelseを追加したもの相当する。

whileに相当

@while(条件)
...繰り返す処理...
@endwhile

繰り返しディレクティブを利用する

index.blade.php
  <body>
    <h1>Blade/Index</h1>
    <p>&#064;foreachディレクティブの例</p>
    <ol>
      @foreach($data as $item)
      <li>{{$item}}</li>
      @endforeach
    </ol>
  </body>

コントローラ

    public function index()
    {
        $data=['one','two','three','four','five'];
        return view('hello.index', ['data'=>$data]);
    }

/helloにアクセスするとリストが表示される。
viewメソッドの[data->\$data]でdataに\$dataを設定してテンプレートに渡している。

$loopによるループ変数

$loopでループに関する情報が得られる。

プロパティ 説明
$loop->index 現在のインデックス(0から開始)
$loop->iteration 現在の繰り返しかず(1から開始)
$loop->remaining 後何回繰り返すか(残り回数)
$loop->count 繰り返しで使っているあ配列の要素数
$loop->first 最初の繰り返しかどうか(最初ならtrue)
$loop->last 最後の繰り返しかどうか(最後ならtrue)
$loop->depth 繰り返しのネスト数
$loop->parent ネストしている場合、親送り返しのループ変数を示す。

例を見てみる。

index.blade.php
  <body>
    <h1>Blade/Index</h1>
    <p>&#064;forディレクティブの例</p>
    @foreach($data as $item)
    @if($loop->first)
    <p>データ一覧</p><ul>
      @endif
      <li>No,{{$loop->iteration}}.{{$item}}</li>
      @if($loop->last)
    </ul><p>ーーーここまで</p>
    @endif
    @endforeach

  </body>

これで確認できる。

phpディテクティブについて

phpディレクティブでPHPスクリプトを直接実行できる。

@php
...PHPスクリプト...
@endphp
index.blade.php
  <body>
    <h1>Blade/Index</h1>
    <p>&#064;phpディレクティブの例</p>
    <ol>
      @php
      $counter=0;
      @endphp
      @while($counter<count($data))
      <li>{{$data[$counter]}}</li>
      @php
      $counter++;
      @endphp
      @endwhile
    </ol>
  </body>

テンプレに用意する@phpは最小限に、が基本

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

laravel7 マルチ認証でログアウト後にログインできなくなる

現象

管理者画面のログアウト後に違うアカウントでログインするとログインできないで「/login」にリダイレクトされる。

ちなみに認証は「laravel/ui 」を利用しています。

原因

ログアウト時にセッションがクリアされていないみたい。

対応

logout時にセッションをクリアするように修正。

--- a/app/Http/Controllers/Admin/Auth/LoginController.php
+++ b/app/Http/Controllers/Admin/Auth/LoginController.php
@@ -7,6 +7,7 @@ use App\Providers\RouteServiceProvider;
 use Illuminate\Foundation\Auth\AuthenticatesUsers;
 use Illuminate\Support\Facades\Request;
 use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Session;

 class LoginController extends Controller
 {
@@ -53,6 +54,7 @@ class LoginController extends Controller
     public function logout(Request $request)
     {
         Auth::guard('admin')->logout();
+        Session::flush();

         return $this->loggedOut($request);
     }

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

tempusdominusのDatatimepickerのスタイルが崩れる

bootstrap4スタイル用のDatetimepickerで利用させてもらっている
tempusdominus版のdatetimepickerモジュールですが、
いつの間にかデザインがずれているのに気づく。

原因はtempusdominus用のスタイル(css)を使用していなかったため

npm install --save tempusdominus-bootstrap-4

laravelならresources/sass/app.scssに以下を追加

@import '~tempusdominus-bootstrap-4/src/sass/tempusdominus-bootstrap-4-build'; 

npm run devしてブラウザの再読み込みで直った。

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

Cannot end a section without first starting one. [Laravel]

Cannot end a section without first starting one.

bladeテンプレートの構文エラーと発生する原因について

発生する原因

シンタックスエラーの可能性:セクションが正しく囲われていないケース

master.blade.php
@section
 記述
@endsection

タイポ:共通レイアウトの呼び出し名が間違っているケース

master.blade.php
// 呼び出した共通レイアウト名が正しいか確認する
@extends('layouts.base') 

タイポ:構文が間違っているケース

master.blade.php
// sectionがsecitonになっている
@seciton('main')
  <p>{{ $msg }}</p>
@endsection
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelのMailで、機能ごとに送信元アドレスを切り替えたい(Gmailなど)時にめっちゃ詰まった話

顧客「Aの機能の時は、hoge@gmail.comで確認メールを送信して欲しいんだよね」

ぼく「わかりました」

顧客「Bの機能の時は、hogehoge@gmail.comで確認メールを送信して欲しいんだよね」

ぼく「承知の助」

1時間後

ぼく「ん?むずくね?」

というわけで、詰まったお話

なんで詰まったのか

LaravelのMailってfromとかのfunctionがあって一見簡単に切り替えられるように見えるんですけど

こんな感じに変えられそう
Mail::from('hogehoge@gmail.com')
      ->to($request->user_mail)
      ->send(new Mail($request->user_name));

まぁ当たり前なんですけど、ダメなわけで

そもそもGmailを使用するには、当然usernameとpasswordを使用しているわけで、それを.envに書いて読み込んでるわけですよね

.env
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME= hoge@gmail.com
MAIL_PASSWORD=passpasspasspasspass
MAIL_ENCRYPTION=ssl

要はこの
MAIL_USERNAMEと
MAIL_PASSWORDはいつ読み込まれて、どうやって変更するんだ!!
って悩んだわけですよ。

参考にさせてもらったのが↓

https://qiita.com/dublog/items/3314ca25a90e76f63b17

かなりわかりやすくプロセスのライフサイクルとサービスコンテナについて書いてくれていて助かりました。

かいつまんで説明すると

・ユーザーからリクエストがくる
・app.phpがMailServiceProviderをコンテナに格納する
・内部でTransportManagerがnewされる(singleton)
・createSmtpDriver()でusernameとpasswordがconfigから読み込まれる
・メールをsendする際に、読み込まれたusernameとpasswordなどからメールが送信される

という流れなわけですよ。

これsingletonとか諸々の影響で、後から書き換えられない(できるかもしれないけど)せいでまぁえらい時間がかかりました。

結局どうやったの?

こうやりました↓

controllerの内部
extract(\Config::get('temp_mail'));

$transport = (new \Swift_SmtpTransport($host, $port))
    ->setUsername($username)
    ->setPassword($password)
    ->setEncryption($encryption);
\Mail::setSwiftMailer(new \Swift_Mailer($transport));
\Mail::to($request->user_mail)
    ->send(new TempMail($request->user_name));
config/temp_mail
<?php

return [
    'host' => 'smtp.gmail.com',
    'port' => '465',
    'username' => 'hogehoge@gmail.com',
    'password' => 'passpasspasspass',
    'encryption' => 'ssl',
];

何をやっているの?

既存のSwiftMailerだと
transport(usernameとかpasswordとか持ってるあれ)
がcontrollerに処理が届く前に、configから読み込まれて上書きできないので
もう1から$transportを作って、 SwiftMailerもろともnewしちゃいましょうって解決法でした。

これは
https://laravel.io/forum/07-22-2014-swiftmailer-with-dynamic-mail-configuration
にて参考にさせてもらいました。

というかね

本当にlaravelのMailでさくっと出来ないんですかね・・・?

めちゃくちゃ頻発する案件だと思うんですが。。

何か他にさくっとできる方法があったら共有していただきたいです。

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

Dockerを使ったphp-fpm(+Laravel)とNginx環境の構築

背景

「LaravelってどうやってHTTPサーバと連携するんだ?」と思い、調査しましたが結構はまったので、自分用のメモも兼ねて記事を書きます。

やりたいこと

・Laravelを使ったWebアプリサーバを立てたい
・Dockerを使って、コマンド一発で楽にWebサーバとWebアプリサーバを立てたい

特にDockerを使ってWebサーバとWebアプリサーバを立てるのは、Kubernetesを使うのに必須だったりするので、鍛錬もかねて、実施しました。

システムの全体図

システムというほどのものではないですが、下記のようにDockerの環境を構築しました。
システム構成図.png

・Nginxのコンテナを立てて、80番と443番でHTTP/HTTPSのリクエストを受け付ける
・NginxのコンテナとWebアプリのコンテナは9000番で通信
・Webアプリのコンテナには、外部から直接アクセスはできない

負荷分散は今回は未実施です。
(勉強のためKubernetesやりたいので、取り組んだらまた投稿します。)

今回もdocker-compose(コンテナをまとめて管理できるツール)を使いました。

Composeファイル

ディレクトリ構造は下記のようにしました。

project/
  ├ nginx/
  │  └ dockerfile
  │  └ server.conf
  ├ php-fpm/
  │  └ dockerfile
  │  └ www.conf
  ├ web/
  │  └ Laravelアプリ/
  ├ docker-compose.yml

docker-compose.ymlは下記のようにしました。

docker-compose.yml
version: '3'
services:
    web:
        build: 
            context: ./nginx
        ports:
            - "80:80"
            - "443:443"
        volumes:
            - ./web/public:/etc/nginx/public
            - ../ssl/certs/:/etc/pki/tls/certs/
            - ../ssl/private/:/etc/pki/tls/private/
        depends_on:
            - app
        container_name: blog_web
    app:
        build: ./php-fpm
        volumes:
          - ./web:/var/www/
        container_name: blog_app

webがNginxコンテナで、appがphp-fpm+laravelのコンテナです。

Nginxコンテナは
・外部から、80番と443番を受け付けるようにし、appと通信できる
・publicディレクトリにおいてある、cssやjsを直接さわれるようにする(staticファイルはnginxから直接返す)
・sslの証明書と鍵は、アクセスの権限を調整して、マウントして見れる

php-fpm+Laravelコンテナは
・ソースをまるまるマウントする

という感じです。

Nginxのコンテナ

下記のように書くだけです。

Dockerfile
FROM nginx:latest

# 設定ファイルを指定の場所に置く
COPY ./server.conf /etc/nginx/nginx.conf


CMD ["nginx", "-g", "daemon off;"]

server.conf
user nginx;

worker_processes auto;
pid /var/run/nginx.pid;

events{
    worker_connections 2048;
    multi_accept on;
    use epoll;
}


http {
    charset UTF-8;
    # versionを表示しない
    server_tokens off;
    include /etc/nginx/mime.types;
    default_type text/plain;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    ssl_protocols TLSv1.1 TLSv1.2;

    server {
            listen 80;
            server_name localhost;
            # 80番でアクセスしてきた人は、443番のリダイレクトを返す
            return 301 https://$host$request_uri;
    }
    server {
            listen 443;
            ssl on;
            server_name  localhost;
            # 証明書
            ssl_certificate /etc/pki/tls/certs/example.crt;
            # 秘密鍵
            ssl_certificate_key /etc/pki/tls/private/example.key;

            # rootディレクトリ
            root /var/www/public;

            add_header X-Frame-Options "SAMEORIGIN";
            add_header X-XSS-Protection "1; mode=block";
            add_header X-Content-Type-Options "nosniff";

            # indexファイルの指定
            index index.php index.html;

            charset utf-8;

            # アクセスしてきたパスに対応するファイルを返す
            location / {
                try_files $uri $uri/ /index.php?$query_string;
            }

            # staticファイルはnginxが直接返す

            # cssファイル
            location /css/ {
               alias public/css/;
            }
            # jsファイル
            location /js/ {
                alias public/js/;
            }
            # imageファイル
            location /images/ {
                alias public/images/;
            }
            # fontsファイル
            location /fonts/ {
                alias public/fonts/;
            }
            # faviconリクエストはログを残さない
            location = /favicon.ico { access_log off; log_not_found off; }

            # php-fpmとの連携
            location ~ \.php$ {
                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 ~ /\.(?!well-known).* {
                deny all;
            }

            # 証明書更新のパスへアクセスできるようにする
            location /.well-known/ {
                alias public/.well-known/;
            }

            # クローラがアクセスできるようにする
            location /robots.txt {
                alias public/robots.txt;
            }

            # サイトマップにアクセスできるようにする
            location /sitemap.xml {
                alias public/sitemap.xml;
            }
    }
}

まだわかっていない箇所もありますが、
Nginxのコンテナは、

fastcgi_pass  app:9000;

とすることで、Webアプリのコンテナのpublicディレクトリのindex.phpをたたきにいけるようにしています。
私はここの記載の仕方でちょっとはまりました。。。

cssファイルやjsファイルなどのstaticファイルは、Nginxコンテナがアプリのpublicディレクトリをマウントしていて、Nginxから直接返せるようにしています。
クローラが見に来れるようにrobots.txtとsitemap.xmlにアクセスできるようにしています。

php-fpm+Laravelコンテナ

dockerfileに入る前に、そもそもphp-fpmとは何ぞ???というのが自分の中であったので、調べました。
公式ドキュメントとか見ましたが、一番わかりやすかったのは他者様の記事

php-fpmとは

php向けのFastCGIで、メモリでキャッシュを保持することで高速にWebサーバ上でPHPを動作させるアプリケーション

ということです。
この他者様の記事ではコンテナ間の通信のことも書いてありましたが、今回Nginxとphp-fpmはTCPで通信しています。
次やる時はUNIXドメインソケットで通信しようかと思います。

さて、Dockerfileですが、下記のようにしました。

Dockerfile
FROM php:7.4.7-fpm

RUN apt update -y
RUN apt install -y libfcgi0ldbl curl git unzip wget vim

# nginxというユーザを作る
RUN useradd -m -s /bin/sh -u 1000 nginx

# 設定ファイルを指定の場所に置く
COPY ./www.conf /usr/local/etc/php-fpm.d/zzz-www.conf

# Composeインストール
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# nodejs 12インストール
RUN apt install -y npm
RUN npm install n -g
RUN n 12

# ユーザ変更
USER nginx

# 作業ディレクトリ
WORKDIR /var/www

VOLUME ["/var/run/php-fpm"]

www.conf
[www]
user = nginx
group = nginx
listen = 9000
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
request_terminate_timeout=30s

php-fpmの設定はwww.confの設定ファイルと公開されているDockerImageで設定は簡単にできました。
あとは、コンテナにComposerとnodejs12を入れたかったので、Dockerfile内でインストールするようにしました。
実際のnpmパッケージのインストールと、composerのビルドは、コンテナを立ち上げた後、コマンドで実行しています。

docker exec blog_app composer install --optimize-autoloader --no-dev
docker exec blog_app npm install
docker exec blog_app npm run production

このようにしたのは、マウント後にnpmパッケージのインストールやcomposerのビルドをしないと、ソースがない時にdockerビルドが走り、失敗してしまうからです。
dockerfile内で、ソースをとってくる処理を書いている場合は、dockerfile内でnpmパッケージのインストールやcomposerのビルドができます。

Composeのビルド

あとは、コンテナのビルド+コンテナ起動して、npmパッケージのインストールやcomposerのビルドして終了です。

docker-compose build

docker-compose up -d

ドメイン設定・SSL対応

ドメインはこちらで買いました。
DNSはAWSのものを使いたかったので、Route53を使用しました。
設定は下記です。
route53_dns.png

DNSの設定はこちらを参考しました。

最低限の設定は下記で、
・Aレコード:グローバルIPとホスト名の紐付け
・NSレコード:ドメインのDNSサーバ(ドメインを購入したサイトのネームサーバにこの値を登録します。)
・SOAレコード:上位のDNSサーバ
オプションで、
・CNAMEレコード:別ホスト名
・TXTレコード:今回はクローラの認証情報記載
を記載しています。

証明書の取得は、お金をかけずにやりたかったので、
他者様の記事を参考にbacmeでオレオレ証書を取得しました。

ホスト名とマシンの紐付けができていれば、やることは単純で、
bacmeをgit cloneしてきて、取得したホスト名で

./bacme -w /var/www/example/ example.com www.example.com

を実行します。wは、.well-knownディレクトリのパスです。
(80番でwell-knownのパスにアクセスできないと↑のコマンドは失敗します。アクセスできるようにNginxの設定を見直してみてください。)
成功すると、bacmeディレクトリ直下に証明書と秘密鍵ができますので、
Nginxのマウント先(/etc/pki/tls/certs/)に指定してください。

参考文献

nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する

調べなきゃ寝れない!と調べたら余計に寝れなくなったソケットの話

Dockerで構築したnginxをSSL化対応する

xdomain

次は、最低限のセキュリティ設定とKubernetesのことを調べます。

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