20200328のlaravelに関する記事は6件です。

LaravelでDBのカラム(mysql)のコメントの作成・確認方法

はじめに

当記事は、Laravelのマイグレーションファイルでカラムにコメントをつける方法、およびmysqlでそのカラムにつけられているコメントを確認する方法を書いています。

経緯

先日、業務内でデータベースのカラムにコメントがつけられていないために、間違った設計を行ってしまったことがありました(自己責任です)。
やはり、テーブル名、カラム名から推測しにくかったり、外部にも同名のカラムがあって、且つ意味するものが違う場合などはカラムにコメントを付けたほうが良いと思います。

マイグレーションファイルの記述

二行目のcomment()でカラムにつけるコメントを記述する。

migration.php
Schema::create('users', function (Blueprint $table) {
    $table->string('name')->comment('名前');
});

MySQLでの確認方法

show full columns from users;

右端のcommentのところに名前と出てますね。

+-------------------+---------------------+--------------------+------+-----+---------+----------------+---------------------------------+---------+
| Field             | Type                | Collation          | Null | Key | Default | Extra          | Privileges                      | Comment |
+-------------------+---------------------+--------------------+------+-----+---------+----------------+---------------------------------+---------+
| name              | varchar(255)        | utf8mb4_unicode_ci | NO   |     | NULL    |                | select,insert,update,references | 名前    |

終わりに

カラムにコメントがあった方が、後々プロジェクトに参加してくるエンジニアさんにも親切ですね。

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

laravel/frameworkとilluminate/supportの依存関係が解決できなかった【小説印刷支援サイト】

承前

投稿した小説をそのまま印刷できるWebサイトを立ち上げようとしている。

開発言語・環境

  • PHP7.2
  • Laravel 6

困ったこと

ライブラリを追加しようとしたところ、いつの間にか composer updateが通らなくなっていた。

現象

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Conclusion: don't install laravel/framework v6.18.3
    - Conclusion: don't install laravel/framework v6.18.2
    - Conclusion: don't install laravel/framework v6.18.1
    - Conclusion: don't install laravel/framework v6.18.0
    - Conclusion: don't install laravel/framework v6.17.1
    - Conclusion: don't install laravel/framework v6.17.0
    - don't install illuminate/support 7.x-dev|don't install laravel/framework 6.x-dev
    - don't install illuminate/support v7.2.0|don't install laravel/framework 6.x-dev
    - don't install illuminate/support v7.2.1|don't install laravel/framework 6.x-dev
    - don't install illuminate/support v7.2.2|don't install laravel/framework 6.x-dev
    - don't install illuminate/support v7.3.0|don't install laravel/framework 6.x-dev
    - Installation request for laravel/framework ~6.17||6.x-dev -> satisfiable by laravel/framework[6.x-dev, v6.17.0, v6.17.1, v6.18.0, v6.18.1, v6.18.2, v6.18.3].
    - Installation request for illuminate/support ~7.2 -> satisfiable by illuminate/support[7.x-dev, v7.2.0, v7.2.1, v7.2.2, v7.3.0].

原因

  • laravel/frameworkのアップデート前に"illuminate/support":"~7.2"を記載してしまった。
  • そもそもilluminate/support 7.*laravel/framework ver6.*をサポートしていなかった。

対処

requireを下記のように修正したところ、composer updateが通るようになった。

composer.json
    "require": {
        "php": "^7.2",
        "fideloper/proxy": "^4.0",
        "laravel/framework": "^6.18",
        "laravel/tinker": "^2.0",
        "webpatser/laravel-uuid": "^3.0",
        "illuminate/support":"^6.0",
        "tecnickcom/tcpdf": "6.2.*"
    }

反省

  • composerは依存関係を解決してくれるが競合はどうしようもない。
  • ので、依存関係は自分で把握する。

Webサイトの公開予定

8~9月頃を予定しています。
段階的に機能開放していくかもです。

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

laravel初心者がバリデーションチェックでうっかりやらかしたこと〜その1

今日はlaravel初心者(筆者)がやりがちなミスを書いていきたいと思います。

バリデーション処理をコントローラーに書いても何の反応も起きない→Viewに表示エラーを出すコードを書いていない

コントローラ側にバリデーション↓だけ書いてもバリデーションエラー表示はされません。

GroupController.php
//コントローラー側抜粋
    public function store(Request $request) {
        $validatedData = $request->validate([
            'title' => 'required|unique:groups,title|max:100',
            'description' => 'required|max:5000',
        ]);

↓をビュー側に書いて表示させましょう

index.html
<!-- index.blade.php の表示させたい側に-->
<small class="text-danger">{{ $errors->first('title') }}</small>
<small class="text-danger">{{ $errors->first('description') }}</small>

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

LaravelでのSession操作まとめ

はじめに

ユーザーの識別やユーザー毎のデータの保存を行うことが必要になるかと思います。
そのような時のために、LaravelフレームワークはSessionという仕組みを提供しており、データ(キーと値のペア)を保存できます。
今回はそのSessionについてまとめたいと思います。

Session操作の種類

Laravelでセッションを操作するには、主に2つの方法があります。
1つはアクションに渡されるRequestインスタンスを経由する方法です。
Illuminate\Session\Storeインスタンスが使われます。

// セッション中の全データを取得する
// Illuminate\Http\Requestをuseする
$data = $request->session()->all();

もう1つはグローバルなヘルパ関数session( )を使用する方法です。
Illuminate\Session\SessionManagerインスタンスが使われます。

// セッション中の全データを取得する
$data = session()->all();

後者の方がコンパクトにかけて便利なので今回は後者についてまとめていきたいと思います。

Session操作の方法

// セッション中の全データを取得する
session()->all();

// 指定したデータがセッションに存在するかを調べる
if (session()->exists('key')) {
    // 存在する
}
if (session()->has('key')) {
    // 存在しnullではない
}

// セッションへデータを保存する
session()->put('key', $value);

// 指定したデータをセッションから削除する
session()->forget('key');

// セッションから全データを削除する
session()->flush();

おわりに

Session使いこなすことでユーザーの識別やユーザー毎のデータの保存ができるようになるのでためしてみてください。

参照:Laravelでsession(セッション)を扱う方法【初心者向け】
   ヘルパ 7.x Laravel

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

サービスコンテナをつついて標準Facadeを差し替えたら思わぬところでドハマリした話

TL;DR

  • サービスコンテナをつついて、標準 Facade の機能を変更する実例です。
  • Facadeはサービスコンテナにアクセスするのでサービスコンテナのオブジェクトを差し替えればファサードも差し替わる。
  • ただしFacade は独自にキャッシュするのでタイミングによってはそれをクリアする必要がある。

なにがしたい?

サービスコンテナの解説記事で「標準機能をそっくり置き換えできる」と書いたことがあって、「ヘルパ関数の置き換え」は記事にしていましたが、今回はその Facade 編です。

テーマは 「テストのときだけトランザクションを実行しないDBファサード」

ことの発端は、テストで使っているトレイト DatabaseTransactions と、テスト対象内の DB::beginTransaction()DB::commit() が衝突を起こしてうまくテストが実行できなかったこと。それ自体は未だ原因がわかっていないのですが諦めて(笑)「いいや、Facadeだし、テスト中だけ Transaction をスルーするオブジェクト作って差し替えよう」と考えた次第です。

ちなみにファサードではなくヘルパ関数 db()->beginTransaction() だったり、DBオブジェクトを受け取って操作 $db->beginTransaction() だったりしても同様に差し替えできます。
が、ファサードに限って、さらにひと工夫必要だという話。

結論

ポイントは「※」のところにある キャッシュクリア です。
これは Facade だけに必要なお作法。

tests/SampleTest.php
use Illuminate\Support\Facades\Facade;
use TestCase;

class SampleTest extends TestCase
{
    use \Illuminate\Foundation\Testing\DatabaseTransactions;

    public function setup()
    {
        parent::setup();

        // サービスコンテナに入っている DatabaseManager を取得する
        $this->defaultDb = $this->app->make('db');

        // 別の DatabaseManager をツッコむ
        $mockDb = new DBConnectionWithoutTransaction($this->defaultDb);
        $this->app->instance('db', $mockDb);

        // Facade 内にキャッシュされたインスタンスを消しておく ※
        // 引数は ↑ のサービスコンテナのラベルと同じ
        Facade::clearResolvedInstance('db');
    }

    public function tearDown()
    {
        // 保存しておいたデフォルトに戻す
        $this->app->instance('db', $this->defaultDb);

        // Facade 内にキャッシュされたインスタンスを消しておく ※
        Facade::clearResolvedInstance('db');

        parent::tearDown();
    }

以下蛇足。

  • Facade::clearResolvedInstanceDB::clearResolvedInstance に置き換え可能です。こうしたほうが余計な use ...\Facade; を追加する手間が省けますが、clearResolvedInstance しなきゃいけないのは DB の機能要件ではなく Facade の特性なので、Facadeと明記して実行すべきかなぁと思ってそうしました。
  • $this->defaultDb は protected で宣言して保護すべきですが、テストクラスなので外からのアクセスとか考えなくていいかなぁと思って省きました。サンプルコードだし。納品するコードにはちゃんと書きますよ!(汗

解説

サービスコンテナに入っているオブジェクトを置き換える

Facade を作る記事でもチラッと書きました が、Facadeの中身はサービスコンテナに納められているオブジェクトなので、そのサービスコンテナのオブジェクトを置き換えると機能の差し替えができます。

よく使う置き換えのパターンは、代替オブジェクトを作って、instance メソッドで渡す方法。

$mockDb = new DBConnectionWithoutTransaction($this->defaultDb);
$this->app->instance('db', $mockDb);

configヘルパを置き換えたとき も同じ方法。

app/Providers/AppServiceProvider.php
$this->app->instance('config', new Repository($this->app->make('config')->all()));

その他の差し替え方法について詳しくは「サービスコンテナ講座 第3回 結合編」に書いていますが、今回のケースでは他の方法はあまり使わないかな。

代替オブジェクトをうまいこと作る方法は、経験と勘(笑)。
今回作ったものは、記事後方で解説しています。

サービスコンテナのラベルを調べる

さらっと 'db' というキーワードが出てきましたが、これはサービスコンテナに入っているDBサービスオブジェクトのラベルです(勝手にラベルと呼んでいますが正式名称は「abstract 抽象」)。これを調べるには Facade のソースファイルを開いて、getFacadeAccessor メソッドを確認します。

vendor\laravel\framework\src\Illuminate\Support\Facades\DB.php
namespace Illuminate\Support\Facades;

class DB extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'db'; // ← これ
    }
}

getFacadeAccessor が返しているのは、そのまんま、サービスコンテナのラベルです。
実際にアプリケーションの適当な場所でこのようにすると

$db = app('db');

データベースオブジェクトのインスタンスが得られるので、ファサードのメソッドを全く同じように使うことができます。

$db->beginTransaction();
$db->getQueryLog();

置き換えるタイミングと注意点

さて話を戻しまして、先程の「サービスコンテナのインスタンスを置き換えるコード」はどこに書くべきでしょうか。

$this->app->instance('db', $mockDb);

アプリケーション全体で差し替えた機能を使いたい場合は、最も良いのは、AppServiceProvider.phpregister() メソッドです。ただしこの時点では他のサービスクラスは初期化(実体化)されていないので、今回のケースのように「すでに初期化されたサービス」を使うには、boot() メソッドに書く必要があります(registerでも動くことがあります。registerよりも前に初期化されるサービスもあるので)。

でも今回はさらに用途が限定されています。
テストの特定メソッドの実行中だけ 差し替えたい。

そのまま考えると、そのテストの実行の直前と直後、setup()メソッドで良い気がします。

tests/SampleTest.php
    public function setup()
    {
        parent::setup();

        $this->defaultDb = $this->app->make('db');
        $mockDb = new DBConnectionWithoutTransaction($this->defaultDb);
        $this->app->instance('db', $mockDb); // ここ
    }

    public function tearDown()
    {
        $this->app->instance('db', $this->defaultDb);

        parent::tearDown();
    }

はい。問題ありません。ほとんどのケースではこれで動きます。

ただ、僕はこれをやって、コケました。壮大にドハマリしました。

問題は、Facadeだけの特別な性質にあります。

それは「Facade で使うインスタンスは、すべて自動的にシングルトンになる」という機能。
つまり内部キャッシュです。

Facade は初回実行時にインスタンスをキャッシュする

わかりやすく書くとこのようなイメージです。

$defaultDb = $this->app->make('db'); // デフォルトインスタンス
$mockDb = new DBConnectionWithoutTransaction($defaultDb); // 代替インスタンス
$this->app->instance('db', $mockDb); // 差し替える

DB::beginTransaction(); // $mockDB->beginTransaction() と等価

サービスコンテナが差し替わると、Facadeで使うインスタンスも差し替わります。
けど、このようにすると

DB::enableQueryLog(); // 差し替え前に1回使っている

$defaultDb = $this->app->make('db'); // デフォルトインスタンス
$mockDb = new DBConnectionWithoutTransaction($defaultDb); // 代替インスタンス
$this->app->instance('db', $mockDb); // 差し替える

DB::beginTransaction(); // $defaultDb ->beginTransaction() と等価

差し替わりません。あれ?おかしいな…。

DB::enableQueryLog(); // 差し替え前に1回使っている

$defaultDb = $this->app->make('db'); // デフォルトインスタンス
$mockDb = new DBConnectionWithoutTransaction($defaultDb); // 代替インスタンス
$this->app->instance('db', $mockDb); // 差し替える

DB::beginTransaction(); // $defaultDb ->beginTransaction() と等価

$witch = $this->app->make('db'); // $mockDb が返ってくる。差し替わっている

サービスコンテナの中身はちゃんと差し替わっています。
ポイントは最初の DB::enableQueryLog(); です。
ファサードのソースコードを見ると、

vendor\laravel\framework\src\Illuminate\Support\Facades\Facade.php
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }
        // すでにキャッシュしていればそれを返す
        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }
        // サービスコンテナからインスタンスを取ってきてキャッシュする
        return static::$resolvedInstance[$name] = static::$app[$name];
    }

このように、サービスコンテナから取得したインスタンスを独自にキャッシュしているんですね。
というわけで、サービスコンテナを差し替えたとき、それよりも前に1回でも同じFacadeを使っているなら、それをクリアする必要があります。

DB::enableQueryLog(); // ここで $defaultDb がキャッシュされる

$defaultDb = $this->app->make('db'); // デフォルトインスタンス
$mockDb = new DBConnectionWithoutTransaction($defaultDb); // 代替インスタンス
$this->app->instance('db', $mockDb); // 差し替える
Facade::clearResolvedInstance('db'); // キャッシュをクリアする

DB::beginTransaction(); // $mockDb->beginTransaction() と等価。差し替わっている。

おまけ DBConnectionWithoutTransaction の中身

ばばばーっと作ったものなのであまりお見せするようなものではありませんが、参考までに。
今回のように「とりあえず必要なメソッドだけラフに置き換えたい」という場合は、このように、

  • 元のクラスの interface を引き継ぐ
  • コンストラクタで元のオブジェクトを受け取れるようにして、メンバ変数にストック(ラッパクラスにする)
  • interface にあるメソッドを実装し、中身はすべて、メンバにストックした元のオブジェクトをコールする。
  • それ以外のメソッドも呼ばれるかもしれないので __call を実装して同様にスルーパス。
  • 置き換えが必要なメソッドを書く。

といった感じにすると、手軽にうまく差し替えできるかと思います。

#テストのときにしか使わないので tests に配置しましたが、ご利用の際はご自由に(^^)

tests\DBConnectionWithoutTransaction.php
namespace Tests;

use Closure;
use Illuminate\Database\ConnectionResolverInterface;

/**
 * トランザクションを抑制したデータベースサービス
 */
class DBConnectionWithoutTransaction implements ConnectionResolverInterface
{
    private $base;

    public function __construct(ConnectionResolverInterface $manager)
    {
        $this->base = $manager;
    }

    public function connection($name = null)
    {
        return $this->base->connection($name);
    }

    public function getDefaultConnection()
    {
        return $this->base->getDefaultConnection();
    }

    public function setDefaultConnection($name)
    {
        return $this->base->setDefaultConnection($name);
    }

    public function __call($method, $parameters)
    {
        return $this->base->$method(...$parameters);
    }

    public function transaction(Closure $callback, $attempts = 1)
    {
        return;
    }

    public function beginTransaction()
    {
        return;
    }

    public function commit()
    {
        return;
    }

    public function rollBack()
    {
        return;
    }

    public function transactionLevel()
    {
        return;
    }
}

感想

以前、サービスコンテナの解説記事で「標準機能をそっくり置き換えできる」と書いたことがあって、実際にそれはとてもカンタンで、余裕でFacadeを置き換えしようとしたら ドハマリ したので、勢いのまま筆を執らせていただきました。何か問題に詰まって検索してたどり着くような記事ではないので、読み物としてサラッと流して、頭の片隅に「こんな記事あったなぁ」と覚えておいていただけると、もしものときに役に立つかもしれません……。

いやー、しかしサービスコンテナはそこそこわかっているつもりでしたが、ただの「つもり」でした。なかなか奥が深い。勉強になりました。

しかし最近は Facade なんて使ってんじゃねぇよ!という風潮もあるようで、それはそれでとても確かなのですが、濫用しなければ、そのすっきりとした記法、作るのがカンタン、AppServiceProviderがカオス化しにくい、「自動シングルトン」や「モック化」といった独自の機能などにはそれなりのメリットがあるんじゃないかと思うので、見捨てないで使ってやってほしいなぁと思っています(いちばんのメリットは「記法がスッキリ」だと思いますけどね。僕はわりと好きです)。

コンストラクタインジェクション(テストしやすく長期メンテナンス性が高い)
use App\MyServices\SampleService;

class MyAppUsecase
{
    protected $sampleService;

    public function __construct(SampleService $sampleService)
    {
        $this->sampleService = $sampleService;
    }

    public function execUsecase()
    {
        $this->sampleService->exec();
    }
Facade(スッキリ)
use App\MyFacades\SampleServiceFacade;

class MyAppUsecase
{
    public function execUsecase()
    {
        SampleServiceFacade::exec();
    }

他にもこんな記事を書いています

Laravelのちょっとマニアックな視点から、誰も書かない記事を書いています(笑
合わせてご覧いただけると幸いです(^^)

ファサードを作る

今回と似た内容の「ヘルパ関数編」

サービスコンテナ講座

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

Laravel 6系以降のauth

使い方、ざっくりした流れ

1.laravel/uiライブラリをインストール

変更されるファイル
・composer.json
・composer.lock

composer require laravel/ui

2.LOGIN機能&テーブル作成

php artisan ui vue --auth

追加されるファイル
app/Http/Controllers/HomeController.php
resources/js/components/ExampleComponent.vue
resources/sass/_variables.scss
resources/views/auth/login.blade.php
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
resources/views/auth/register.blade.php
resources/views/auth/verify.blade.php
resources/views/home.blade.php
resources/views/layouts/app.blade.php

変更されるファイル
package.json
resources/js/app.js
resources/js/bootstrap.js
resources/sass/app.scss
routes/web.php
webpack.mix.js

3.packageインストール(npmが必要)

npm install

4.CSS/JS 作成ビルド

npm run dev

laravel5系の場合

Laravel5系の場合は下記コマンドで6系、7系の上記コマンドと同じ環境になる

php artisan make:auth

認証メソッド

controller.php
use Illuminate\Support\Facades\Auth;

// 現在認証されているユーザーの取得
$user = Auth::user();

// 現在認証されているユーザーのID取得
$id = Auth::id();

// 認証チェック
if (Auth::check()) {
    // ユーザーはログインしている
} else {
  // ログインしていません
}

上記のように認証ユーザを取得してAuth::check()メソッドを使うとログイン済かログイン未なのかがチェックできるので、それに応じてログインページにリダイレクトさせたりできる。

デフォルト設定なら自動的にルーティングやコントローラが作成されているはずなので下記のようにしてログイン未ならリダイレクトとかやれば良いかとおもう。(ルーティングやコントローラは自身の環境確認してください)

return redirect('/login');

return redirect('/register');

※調べたらルーティングにmiddleware('auth')を追加するだけでログインフォームにリダイレクトされるみたいですね。
下記にまとめときます

特定ページに画面遷移したとき、ログインしてない場合はリダイレクトさせる方法

ルーティングの後にmiddleware('auth')を追加するだけ

Route::get('/hoge', 'HogeController@index')->middleware('auth');

ログイン処理の実装

# Auth::attempt()メソッドを使う

    if (Auth::attempt(['email' => $email,
                       'password' => $password])) {
                         $msg = "ログインしました。'( . Auth::user()->name . ')";
                       } else {
                         $msg = "ログインに失敗しました。";

controller.php
  public function getAuth(Request $request)
  {
    $params = ['message' => 'ログインしてください'];
    return view('hello.auth', $params);
  }

  public function postAuth(Request $request)
  {
    $email = $request->email;
    $password = $request->password;
    if (Auth::attempt(['email' => $email,
                       'password' => $password])) {
                         $msg = "ログインしました。'( . Auth::user()->name . ')";
                       } else {
                         $msg = "ログインに失敗しました。";
                       }
                       return view('hello.auth', ['message' => $msg]);
  }

独自で作ったログイン画面を使うこともできるし、デフォルトのログイン画面をカスタムして使うこともできるので、やり方次第でなんでもできそう。

参考サイト

https://qiita.com/rei67/items/d6d0f5f6e58edbb17c09

https://qiita.com/ucan-lab/items/bd0d6f6449602072cb87

公式:https://readouble.com/laravel/7.x/ja/authentication.html

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