20200710のlaravelに関する記事は9件です。

Disable updating updated_at from Laravel Admin

Summary

If you'd like to stop updating updated_at column from Laravel Admin page, You can code like this

$form->model()->timestamps = false;

Usage

app/Admin/Controllers/SomeController.php
    /**
     * Make a form builder.
     *
     * @return Form
     */
    protected function form()
    {

        ...

        $form->saving(function (Form $form) {

            $form->model()->timestamps = false;

        });

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

laravel(npm)で追加したjsモジュールを外部のjsで使う

laravelはnpmによるjavascriptのモジュールのパッケージングを利用できるが
ソース修正の度にnpm run dev(prod)でコンパイルしなければならないし
画面毎のjsファイルなどを配置したい場合などファイルが複数になる場合、
パッケージングを分けることはできるが、管理がめんどいので外部jsを呼べるようにする。

layouts/app.blade.php@yield('inc-javascript')を追加
あと、jsの読み込みはすべてdeferをつける。

layouts/app.blade.php
<html>
...
<head>
...
  <!-- Scripts -->
  <script src="{{ asset('js/app.js') }}" defer></script>
  @yield('inc-javascript')

...
</head>

各ページのbladeファイルで@section('inc-javascript')でjsファイルを指定する

index.blade.php
@extends('layouts.app')
@section('inc-javascript')
<script src="{{ asset('/js/hogehoge.js') }}" defer></script>
...
@endsection

app.js内のモジュールをを使う

resources/js/bootstrap.jsでrequireする。
必要ならグローバル変数に格納する。

bootstrap.js
require('moment');
globals.moment = require('moment');
window.moment = require('moment');
var moment = require('moment');

npm run dev(prod)public/js/app.jsにパッケージされる。

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

【Laravel】レイアウトの作成

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

Bladeには例アンツを継承してテンプレをセクションとして組み合わせてレイアウトを作成する機能がある。

レイアウトの作成

レイアウトの定義と継承

多くのページがあるサイトでは共通したデザインが表示される。
サイト全体を統一したデザインでレイアウトするためにBladeには継承とセクションがある。

継承とは

PHPのクラスの継承と同じ。
既存のテンプレを継承して新しいテンプレを作ること

セクションとは

継承でページをデザインするとき、ページ内の要素として活用されてるもの。

@secsion@yield

セクション利用のための2つのディレクティブ

@section

レイアウトで様ざなま区画を定義する。

@section(名前)
...表示内容...
@endsection

これで指定した名前でセクションが用意される。
セクションは同じ名前の@yieldにはめ込まれ表示される。

@yield

セクション内容をはめ込んで表示する。

@yield(名前)

@yieldは記載場所を示すもので@endyieldはない

ページレイアウトを作成する

例を見てみる。
resources/views/layoutでhelloapp.balde.phpを作成する。
そこに書いていく

helloapp.balde.php
<html >
  <head>
    <title>@yield('title')</title>
    <style>
    body{
      font-size:16pt;color:#999;margin:5px;
    }
    h1{
      font-size:50px;
      text-align:right;
      color:#f6f6f6f6;
      margin:-20px 0px -30px 0px;
      letter-spacing:-4pt;
    }
    ul{
      font-size:12pt;
    }
    hr{
      margin:25px 100px;
      border-top:1px dashed #ddd;
    }
      .menutitle{
        font-size:14px;
        font-weight:bold;
        margin:0px;
      }
      .content{
        margin:10px;  
      }
      .footer{
        text-align:right;
        font-size:10pt;
        margin:10px;
        border-bottom:solid 1px #ccc;
        color:#ccc
      }

    </style>
  </head>
  <body>
    <h1>@yield('title')</h1>
    @section('menubar')
    <h2 class="menutitle">メニュー</h2>
    <ul>
      <li>@show</li>
    </ul>
    <hr size="1">
    <div class="content">
      @yield('content')
    </div>
    <div class="footer">
      @yield('footer')
    </div>
  </body>
</html>

ここでいくつかのディレクティブが設定されている。

@section('menubar')

これはメニュー表示の区画。

セクションは玖珂を定義するものだが、一番土台とアンルレイアウトで@sectionを用意する場合は。@endsectionではなく@showでセクションを終わりにする。

継承レイアウトの作成

index.balde.php
@extends('layouts.helloapp')

@section('title','Index')

@section('menubar')
  @parent
  インデックスページ
@endsection

@section('content')
  <p>ここが本文のコンテンツ</p>
  <p>必要なだけ記述ができる</p>
@endsection

@section('footer')
  copyright 2020 tuyano.
@endsection

HTMLらしくない表記になった。

@extendsについて

@extends('layouts.helloapp')

layoutsフォルダのhelloapp.blade.indexファイルをロードし、親レイアウトとして継承する。

@sectionの書き方

2つの方法がある。

単純に表示させるだけ

@section('title','Index')

'title'という名前のセクションに'Index'というテキスト値を設定。

@endsectionを併用した書き方

@section('menubar')
  @parent
  インデックスページ
@endsection

親レイアウトに'menubar'という@yieldがあればそこにはめ込まれて表示されるわけだが、
親レイアウトに'menubar'という@yieldはなく、@sectionがある。
この場合@sectionは上書きされる。

@parentは親レイアウトのセクションを示す。
@sectionの上書きがされるとき、親のセクションも残したい場合は@parentで親のセクションをはめ込んで表示できる。

これで表示してみるとhelloapp.blade.phpに、index.blade.phpに用意したセクションがはめ込まれて表示されていることが確認できる。

このようにレイアウトを継承するkとで子にはセクションに表示する内容だけを書けばいいことになり、お暗示レイアウトでページが表示される。

コンポ−ネントについて

継承を利用することで全体を同じデザインにすることができるが、時には一部を切り離したいこともある。

コンポ−ネントは1つのテンプレとして独立して用意されるレイアウト用の部品。

@componentディレクティブ

コンポーネントは普通のテンプレとして作成する。書き方に違いはない。
作成したコンポーネントは@componentで表示場所を設定できる。

@component(名前)
...コンポネネントの表示内容
@component

名前はviewsフォルダのファイル名で指定される。
/views/components/ok.blade.php
ならcomponents.okで指定できる。

コンポーネントを作成する

message.blade.php
<style media="screen">
    .message {
        border: double 4px #ccc;
        margin: 10px;
        padding: 10px;
        background-color: #fafafa;
    }

    .msg_title {
        margin: 10px 20px;
        color: #999;
        font-size: 16pt;
        font-weight: bold;
    }

    .msg_content {
        margin: 10px 20px;
        color: #aaa;
        font-size: 12pt;
    }
</style>
<div class="message">
    <p class="mes_title">{{$msg_title}}</p>
    <p class="msg_content">{{$msg_content}}</p>
</div>

コンポーネントを読み込む

messageコンポーネントをテンプレにに読み込んで表示する。
@section('content')の部分を修正

index.blade.php
@section('content')

  <p>ここが本文のコンテンツ</p>
  <p>必要なだけ記述できる</p>

  @component('components.message')
    @slot('msg_title')
    CAUTION!
    @endslot

    @slot('msg_content')
    これはメッセージの表示
    @endslot
  @endcomponent

@endsection

スロットについて

\$msg_titleや\$msg_contentに値を送っているのがスロット
{{}}で指定した変数に値を設定する。

@slot(名前)
...設定する内容
#endslot
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelのGateでロール毎に権限を分ける

今回設定するrole

今回はレベル0が管理者でなんでも出来るユーザー。あとは1-4まで分けようと思います。
調査の段階なので、管理者と一般ユーザーとしてレベル4が分かれているのを確認できれば良しとします。
ちなみにプロジェクト名はappとしています。各ファイルのパスの最初に出てくるところがプロジェクトの名前になってますね。

開発環境

Laradockです。
PHP 7.4, MySQL 8.0, Laravel 6 で実行
構築は以下の手順で実施。
バージョンはそれぞれ自分が使いたい環境に合わせる。
https://qiita.com/ryuseino/items/e0e3a77245635b7cc101

カラム追加用のマイグレーションファイルを作成

今回はユーザーテーブルにroleを追加して、それを使ってアクセス出来るページを制御してみます。
まずはファイルを作成。

php artisan make:migration add_column_role_users_table --table=users

出来たファイル編集

app/database/migrations/2020_07_08_103224_add_column_role_users_table.php
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->tinyInteger('role')->default(4)->after('password')->index('index_role')->comment('ロール');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('role');
        });
    }

ファイル編集が終わったらmigrate実行してカラムを追加しておきます。

 php artisan migrate

既存のファイルの編集

今回は管理者のみが見れるadmin-onlyと一般ユーザーでも見れるuser-higherを追加。

app/app/Providers/AuthServiceProvider.php
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // adminに許可
        Gate::define('admin-only', function ($user) {
            return ($user->role == 0);
        });
        // 一般ユーザに許可
        Gate::define('user-higher', function ($user) {
            return ($user->role <= 5);
        });
    }

admin用のページ作成

今回は通常のHomeを複製してからファイル名やクラス名、読み込むviewなど変更します。
作ったファイルのパスは以下。中身は省略。基本的にhomeとなってたところをadminにしただけです。
viewの方は表示される文字列を変えておくと分かりやすくていいかも知れません。

app/app/Http/Controllers/AdminController.php
app/resources/views/admin.blade.php

管理者だけ見れる文字列をテンプレートに追加

ページ毎の制御ではなく、ページの内容も分けることが出来ます。
今回はHomeの方で管理者だけに表示されるメッセージを追加。

app/resources/views/home.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">Dashboard</div>

                    <div class="card-body">
                        @if (session('status'))
                            <div class="alert alert-success" role="alert">
                                {{ session('status') }}
                            </div>
                        @endif

                        You are logged in!
                        @can('admin-only')
                            <div class="alert alert-dark" role="alert">
                                管理者だけですよ
                            </div>
                        @endcan
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

@can@endcanの間が管理者のみ表示されるメッセージですね。

ユーザーのroleを設定して確認

2つユーザー登録し、roleを片方は0(管理者)にして、もう片方は4(デフォルトのユーザーレベル)に設定。
その後それぞれのユーザーでログインします。

localhost/home にアクセスした場合

一般ユーザー -> デフォルトのホーム画面が出る
管理者 -> 追加で管理者用の文字列が出る

localhost/admin にアクセスした場合

一般ユーザー -> アクセス出来ない (403の画面)
管理者 -> アクセス出来る

参考にしたページ

以下のページが大変分かりやすかったです。
https://www.ritolab.com/entry/56

こちらはもう少し細かいところも書いてありますね。
https://readouble.com/laravel/6.x/ja/authorization.html

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

Laravel ログイン時に処理を加える。

Laravel 5.5
PHP 7

ログインと同時に処理を行いたいと思い、
調べたのでここに記したいと思います。

vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.phpに
ログイン処理のリダイレクト直前で実行される authenticated() メソッドが書かれていますが、
何も設定されていません。

vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php
/**
 * The user has been authenticated.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  mixed  $user
 * @return mixed
 */

protected function authenticated(Request $request, $user)
{
    //
}

これをオーバーライドする形で処理を実装します。
LoginContorollerに処理を実装します。
例、
user情報のtypeが0の人がログインした場合
typeを20に設定するという処理を追加。

Auth/LoginController.php
use App\User;

protected function authenticated(\Illuminate\Http\Request $request, $user)
{
    if ($user->authority == 0) {
        $user->authority = 20;
        $user->save();
}

$requestには、ログイン画面で入力した、メールアドレス(又は、ユーザーネーム)とパスワードが代入されています。
$userには、Userテーブルのユーザー情報が代入されています(ログインユーザーの情報)。
※この変数名は固定です。仮にcustomerテーブルにユーザー情報を保存していたとしてもデータは、$userに代入されます。

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

Laravel ビューファイルで作成する画面にYoutubeの動画を埋め込む

目的

  • Youtubeの再生画面をLaravelのビューファイルに埋め込む方法をまとめる。

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

  • 実施環境のLaravelアプリ動作環境が構築できていること。

前提情報

  • 特になし

読後感

  • Laravelのビューファイルを用いた画面の表示にYoutubeの再生ウインドウを埋め込むことができる。

概要

  1. 埋め込みリンクのコピー
  2. ビューファイルの記載

詳細

  1. 埋め込みリンクのコピー

    1. ブラウザにてYoutubeのページを開く。
    2. 埋め込みたい動画の「共有」をクリックする。

      Awesome_City_Club_–_今夜だけ間違いじゃないことにしてあげる__Music_Video__-_YouTube.png

    3. 「埋め込む」をクリックする。

      Awesome_City_Club_–_今夜だけ間違いじゃないことにしてあげる__Music_Video__-_YouTube_?.png

    4. 表示されたhtml形式のリンクをコピーする。

      Awesome_City_Club_–_今夜だけ間違いじゃないことにしてあげる__Music_Video__-_YouTube-2.png

  2. ビューファイルの記載

    1. Youtubeを埋め込みたいビューファイルを開き、埋め込みたい箇所に先にコピーしたリンクを貼り付ける。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel クエリビルダを駆使する

目的

  • クエリビルダを用いたDBのデータ取得方法とテーブル結合を用いたデータん取得方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提環境

  • 先に記載した実施環境が構築されていること。
  • 実施環境にて何かしらのLaravelアプリが作成されていること。
  • Laravelアプリ用のDBが作成され.envファイルに必要情報が記載され、マイグレーションなどが行えること。

前提情報

  • contentsテーブルとstatusesテーブルを新たに作成し、テーブル内に格納されているデータをクエリビルダを用いて取得する。
  • コードの解説は本記事の下部の「ミニ解説」にてちょっとだけ解説している。

読後感

  • contentsテーブルの内容をクエリビルダを用いて取得する。
  • contentsテーブルとstatusesテーブルを結合し内容をクエリビルダを用いて取得する。

クエリビルダとは?

  • LaravelのDBアクセスの手法の一つである。
  • クエリ文を先に組み立ててからDBにアクセスする方法である。
  • 使用する句はSQLと共通であるが記載方法に若干のクセがある。

概要

  1. contentテーブルの作成
  2. statusesテーブルの作成
  3. ルーティングの記載
  4. コントローラの作成と記載(クエリビルダの記載)
  5. ビューの作成と記載
  6. データの格納
  7. クエリビルダ単体の確認
  8. JOIN句の確認

詳細

  1. contentテーブルの作成

    1. アプリ名ディレクトリで下記コマンドを実行してモデルファイルとマイグレーションファイルを作成する。

      $ php artisan make:model Content --migration
      
    2. アプリ名ディレクトリで下記コマンドを実行してマイグレーションファイルを開く。

      $ vi database/migrations/YYYY_MM_DD_XXXXXX_create_contents_table.php
      
    3. 開いたマイグレーションファイルを下記の様に追記修正する。

      アプリ名ディレクトリ/database/migrations/YYYY_MM_DD_XXXXXX_create_contents_table.php
      <?php
      
      use Illuminate\Database\Migrations\Migration;
      use Illuminate\Database\Schema\Blueprint;
      use Illuminate\Support\Facades\Schema;
      
      class CreateContentsTable extends Migration
      {
          /**
           * Run the migrations.
           *
           * @return void
           */
          public function up()
          {
              Schema::create('contents', function (Blueprint $table) {
                  $table->id();
                  //下記を追記
                  $table->string('content');
                  $table->integer('status_id');
                  //上記までを追記
                  $table->timestamps();
              });
          }
      
          /**
           * Reverse the migrations.
           *
           * @return void
           */
          public function down()
          {
              Schema::dropIfExists('contents');
          }
      }
      
    4. アプリ名ディレクトリで下記コマンドをジックしてマイグレーションを実行する。

      $ php artisan migrate
      
  2. statusesテーブルの作成

    1. アプリ名ディレクトリで下記コマンドを実行してモデルファイルとマイグレーションファイルを作成する。

      $ php artisan make:model Status --migration
      
    2. アプリ名ディレクトリで下記コマンドを実行してマイグレーションファイルを開く。

      $ vi database/migrations/YYYY_MM_DD_XXXXXX_create_statuses_table.php
      
    3. 開いたマイグレーションファイルを下記の様に追記修正する。

      アプリ名ディレクトリ/database/migrations/YYYY_MM_DD_XXXXXX_create_contents_table.php
      <?php
      
      use Illuminate\Database\Migrations\Migration;
      use Illuminate\Database\Schema\Blueprint;
      use Illuminate\Support\Facades\Schema;
      
      class CreateContentsTable extends Migration
      {
          /**
           * Run the migrations.
           *
           * @return void
           */
          public function up()
          {
              Schema::create('contents', function (Blueprint $table) {
                  $table->id();
                  //下記を追記
                  $table->string('status_name');
                  $table->timestamps();
              });
          }
      
          /**
           * Reverse the migrations.
           *
           * @return void
           */
          public function down()
          {
              Schema::dropIfExists('contents');
          }
      }
      
    4. アプリ名ディレクトリで下記コマンドをジックしてマイグレーションを実行する。

      $ php artisan migrate
      
  3. ルーティングの記載

    1. アプリ名ディレクトリで下記コマンドを実行してルーティングファイルを開く。

      $ vi routes/web.php
      
    2. 下記の一行を追記する。

      アプリ名ディレクトリ/routes/web.php
      Route::get('/output', 'ContentController@output');
      
  4. コントローラの作成と記載(クエリビルダの記載)

    1. アプリ名ディレクトリで下記コマンドを実行してコントローラファイルを作成する。

      $ php artisan make:controller ContentController
      
    2. アプリ名ディレクトリで下記コマンドを実行してコントローラファイルを開く。

      $ vi app/Http/Controllers/Controller.php
      
    3. 下記の様にoutputという名前のアクションを追加する。

      アプリ名ディレクトリ/app/Http/Controllers/Controller.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      //下記を追記
      use App\Content;
      
      class ContentController extends Controller
      {
          //下記を追記
          public function output()
          {
              $contents_query = Content::select('*');
              $contents = $contents_query->get();
              return view('contents.output', [
                  'contents' => $contents,
              ]);
          }
          //上記までを追記
      }
      
  5. ビューの作成と記載

    1. アプリ名ディレクトリで下記コマンドを実行してビューファイルを格納するディレクトリを作成する。

      $ mkdir resources/views/contents
      
    2. アプリ名ディレクトリで下記コマンドを実行してビューファイルを作成して開く。

      $ vi resources/views/contents/output.blade.php
      
    3. ビューファイルに下記を記載する。

      アプリ名ディレクトリ/resources/views/contents/output.blade.php
      @foreach ($contents as $content)
          <hr>
          <p>{{$content['content']}}</p>
          <p>{{$content['status']}}</p>
      @endforeach
      
  6. データの格納

    1. アプリ名ディレクトリで下記コマンドを実行してtinkerを開く。

      $ php artisan tinker
      
    2. tinkerにて下記を実行してcontentsテーブルにデータを追加する。

      use App\Content;
      $a = new Content();
      $a->content = 'test';
      $a->status_id = 1;
      $a->save();
      
    3. tinkerにて下記を実行してstatusesテーブルにデータを追加する。

      use App\Status;
      $b = new Status();
      $b->status_name = 'good'
      $b->save();
      $c = new Status();
      $c->status_name = 'bad'
      $c->save();
      
  7. クエリビルダ単体の確認

    1. アプリ名ディレクトリで下記コマンドを実行しローカルサーバを起動する。

      $ php artisan serve
      
    2. 下記にアクセスし、ブラウザから当該アプリを確認する。

    3. 下記の様にブラウザで表示されることを確認する。

      127_0_0_1_8000_output.png

  8. JOIN句の確認

    1. アプリ名ディレクトリで下記コマンドを実行してコントローラファイルを開く。

      $ vi app/Http/Controllers/Controller.php
      
    2. 開いたコントローラファイルのoutputアクション内部を下記の様に修正する。

      アプリ名ディレクトリ/app/Http/Controllers/Controller.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      use App\Content;
      
      class ContentController extends Controller
      {
          public function output()
          {
              $contents_query = Content::select('*');
              //下記を追記
              $contents_query->join('statuses', 'contents.status_id', '=', 'statuses.id');
              $contents = $contents_query->get();
              return view('contents.output', [
                  'contents' => $contents,
              ]);
          }
      }
      
    3. アプリ名ディレクトリで下記コマンドを実行してビューファイルを開く。

      $ vi resources/views/contents/output.blade.php
      
    4. ビューファイルに下記を下記の様に修正する。

      アプリ名ディレクトリ/resources/views/contents/output.blade.php
      @foreach ($contents as $content)
          <hr>
          <p>{{$content['content']}}</p>
          <!-- 下記を追記 -->
          <p>{{$content['status_name']}}</p>
      @endforeach
      
    5. 下記にアクセスし、ブラウザから当該アプリを確認する。

    6. 下記の様にブラウザで表示されることを確認する。

      127_0_0_1_8000_output.png

ミニ解説

「クエリビルダ単体の確認」のアクション内のコードについて

  • 下記にコントローラのアクションのコードを記載する。

    アプリ名ディレクトリ/app/Http/Controllers/Controller.php
    public function output()
    {
        //DBにアクセスするためのクエリ文を組み立てている
        //Contentというモデルファイルに紐づいたテーブルの全てのカラムを取得する(SQL文のselect *と同じ)クエリ文を変数$contents_queryに格納している
        $contents_query = Content::select('*');
        //先に組み立てたクエリ文を実行して取得したデータを変数$contentsに格納している。
        $contents = $contents_query->get();
        return view('contents.output', [
            'contents' => $contents,
        ]);
    }
    

「JOIN句の確認」のアクション内のコードについて

  • 下記にコントローラのアクションのコードを記載する。

    アプリ名ディレクトリ/app/Http/Controllers/Controller.php
    public function output()
    {
        //DBにアクセスするためのクエリ文を組み立てている
        //Contentというモデルファイルに紐づいたテーブルの全てのカラムを取得する(SQL文のselect *と同じ)クエリ文を変数$contents_queryに格納している
        $contents_query = Content::select('*');
        //テーブルの結合を行う
        //クエリ文が格納されている変数->join('結合するテーブル名', '結合元テーブル名.結合するカラム名', '=', '結合先テーブル名.リンクするカラム名')
        $contents_query->join('statuses', 'contents.status_id', '=', 'statuses.id');
        //先に組み立てたクエリ文を実行して取得したデータを変数$contentsに格納している。
        $contents = $contents_query->get();
        return view('contents.output', [
            'contents' => $contents,
        ]);
    }
    
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-compose.ymlのbuild設定はとりあえずcontextもdockerfileも埋めとけって話

docker-composeを使った開発では以下の2つのディレクトリ構成になっていることが多いです。

サービスディレクトリ直下にDockerfile
.
├ app
│ ├ Dockerfile
│ └ ...その他ファイル群
├ api
│ ├ Dockerfile
│ └ ...その他ファイル群
├ nginx
│ ├ Dockerfile
│ └ ...その他ファイル群
└ docker-compose.yml
dockerディレクトリ下にサービス毎にDockerfile
.
├ app/
├ api/
├ nginx/
├ docker
│ ├ app
│ │ └ Dockerfile
│ ├ api
│ │ └ Dockerfile
│ └ nginx
│   └ Dockerfile
└ docker-compose.yml  

Dockerの コンテキスト という概念を知っていないと、ディレクトリ構成が違うだけで何度もコンテキスト周りのエラーで悩まされることがあります(1敗)。
なので自分的結論を出してみました。

TL;DR

docker-compose を使った開発では

docker-compose.yml
version: "3"
services:
  nginx:
    build: ./docker/nginx/

のようにDockerfileがあるディレクトリを指定するだけでなく

docker-compose.yml
services:
  nginx:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile

のようにコンテキストをルートディレクトリに指定して、Dockerfileの場所も直接指定しておけばおk

Dockerの「コンテキスト」とは?

docker build コマンドを実行したときの、カレントなワーキングディレクトリのことを ビルドコンテキスト(build context)と呼びます。 デフォルトで Dockerfile は、カレントなワーキングディレクトリにあるものとみなされます。 ただしファイルフラグ(-f)を使って別のディレクトリとすることもできます。 Dockerfile が実際にどこにあったとしても、カレントディレクトリ配下にあるファイルやディレクトリの内容がすべて、ビルドコンテキストとして Docker デーモンに送られることになります。

Dockerfile 記述のベストプラクティス

これをさらに要約すれば 「docker buildコマンドを実行した場所」ってことですね。

docker build コマンドを実行した場所ってことなので、docker buildコマンドはDockerfileがあるディレクトリで実行すれば問題なさそうですね。

しかし、docker-compose コマンドを使って開発している場合はどうでしょうか?
Dockerfileがあるディレクトリでコマンドを実行することってほとんど無いと思います。その場合はコンテキストについてどう考えればいいのでしょうか?

サービス直下にDockerfileを置く場合

例として「Laravel, Nginx」というよくあるプロジェクトの構成で考えてみます。
ディレクトリ構成は以下の様になります。

.
├ api
│ ├ Dockerfile
│ ├ app
│ ├ bootstrap
│ └ ...その他のLaravelファイル
├ nginx
│ ├ Dockerfile
│ └ default.conf
└ docker-compose.yml

このディレクトリ構成の場合、api/Dockerfilenginx/Dockerfileのコンテキストはそれぞれどこになるか分かりますか?
docker buildコマンドを実行するapi/nginx/ディレクトリ?

nginx/ディレクトリがコンテキストだとするとnginx/Dockerfileは以下のようになります。

nginx/Dockerfile
FROM nginx:1.18-alpine
ADD ./default.conf /etc/nginx/conf.d/default.conf

RUN mkdir -p /var/www/public
ADD ../api/public /var/www/public

以下の1文に注目してください。コンテキストが nginx/なのに、 ../api/public で分かる通り、コンテキストのディレクトリから外れたファイルを参照していますね。

nginx/Dockerfile
ADD ../api/public /var/www/public

このまま実行すると

ERROR: Service 'php' failed to build: COPY failed: stat /var/lib/docker/tmp/docker-builder115741816/api: no such file or directory

のようなエラーが出ます。

Dockerはコンテキスト(カレントディレクトリ)の外のファイルにはアクセスできない仕様なのです。そこら辺に関しては以下の記事で詳しく説明されています。

https://qiita.com/toshihirock/items/c85f3eb5f4752b15ca3d

ではどうやってnginx/のコンテキストからapi/のファイルにアクセスすればいいのでしょうか?
答えは簡単です。

コンテキストをルートディレクトリにすればいいのです

docker-compose.ymlで「コンテキスト」と「Dockerfileのある場所」を直接指定してみましょう。

docker-compose.ym;
version: "3"
services:
  nginx:
    build:
      context: .
      dockerfile: ./nginx/Dockerfile
    ports:
      - 8080:80
    depends_on:
      - php
  php:
    build:
      context: .
      dockerfile: ./api/Dockerfile
    ports:
      - 9000:9000

コンテキストはどちらのサービスも

build:
      context: .

docker-compose.ymlがあるルートディレクトリに設定。

Dockerfileの場所は

dockerfile: ./nginx/Dockerfile
dockerfile: ./api/Dockerfile

でそれぞれ指定。

dockerディレクトリにDockerfileをまとめた場合

では次にdocker というディレクトリを作って、その中に各サービスのDockerfileをまとめた構成を考えてみます。

.
├ api
│ └ Laravelのファイル群
├ nginx
│ └ default.conf
├ docker
│ ├ php
│ │ └ Dockerfile
│ └ nginx
│   └ Dockerfile
└ docker-compose.yml

先ほどと同様にコンテキストを docker/phpdocker/nginxと考えた場合、どうやってもうまくいきません。
このディレクトリ構成の場合は、そもそもDockerfileからコンテキスト外のサービスのファイル群が入っている api/nginx/に一切アクセスできません。

ここでも同様docker-compose.ymlでコンテキストをルートディレクトリに、Dockerfileの位置も直接指定する必要がありそうです。

docker-commpose.yml
version: "3"
services:
  nginx:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
    ports:
      - 8080:80
    depends_on:
      - php
  php:
    build:
      context: .
      dockerfile: ./docker/api/Dockerfile
    ports:
      - 9000:9000

注意点

ルートディレクトリをDockerのコンテキストにすることで、Dockerfileはどんなファイルにもアクセスできるようになりました。
一方で、build時はその分Dockerデーモンという奴にそれだけ多くのファイルを送ることになるので遅くなることがあるようです。

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

データプロバイダでテストに値を入れる

phpunitのデータプロバイダについての基本的な使い方のメモです。
PHPUnit 用のテストの書き方

データプロバイダとは

同じアサーションで値だけ変えたい場合にデータプロバイダを使うと便利です。
テストに値を与える部分を別のメソッドに切り出すことができます。

データプロバイダの配列の中身が引数としてテストメソッドに渡され、メソッド内の配列の分だけアサーションが反復処理されます。

下の例ではassertSame()が4回呼ばれることになります。

<?php
use PHPUnit\Framework\TestCase;

class DataTest extends TestCase
{
    /**
     * @dataProvider additionProvider
     */
    public function testAdd($a, $b, $expected)
    {
        $this->assertSame($expected, $a + $b);
    }

    public function additionProvider()
    {
        return [
            [0, 0, 0],
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 3]
        ];
    }
}

@dataProviderアノテーションでデータプロバイダメソッドを指定します。

/**
 * @dataProvider additionProvider
 */

データプロバイダーの返り値は配列にする必要があります。

return [
    [0, 0, 0],
    [0, 1, 1],
    [1, 0, 1],
    [1, 1, 3]
];

各データセットに名前を付ける

データセットには名前を付けることができます。テストに失敗した場合、データセットの名前が表示されるため、どのデータで失敗したのかわかりやすくなります。

<?php
use PHPUnit\Framework\TestCase;

class DataTest extends TestCase
{
    /**
     * @dataProvider additionProvider
     */
    public function testAdd($a, $b, $expected)
    {
        $this->assertSame($expected, $a + $b);
    }

    public function additionProvider()
    {
        return [
            'adding zeros'  => [0, 0, 0],
            'zero plus one' => [0, 1, 1],
            'one plus zero' => [1, 0, 1],
            'one plus one'  => [1, 1, 3]
        ];
    }
}
$ phpunit DataTest
PHPUnit 4.6.0 by Sebastian Bergmann and contributors.

...F

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) DataTest::testAdd with data set "one plus one" (1, 1, 3)
Failed asserting that 2 is identical to 3.

/home/sb/DataTest.php:9

FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む