20200326のlaravelに関する記事は15件です。

Laravel Paginate

ページネーションメソッドを使ってページャの実装

HogeController.php
    use App\Models\Hoge; #Hogeモデルがある前提

    $hoges = Hoge::paginate(5);
    return view('hoge.index', ['hoges' => $hoges]);
style.scss
  .page ul li{
    display: inline;
  }
hoge/index.blade.php
  @foreach($hoges as $hoge)
    <tr>
      <td>{{ $hoge->id }}</td>
      <td>{{ $hoge->name }}</td>
      <td>{{ $hoge->email }}</td>
      <td>{{ $hoge->age }}</td>
    </tr>
  @endforeach

<div class="page">
    {{ $hoges->links() }}
</div>

1.コントローラでpaginateメソッドを使って表示件数を指定
2.viewでlinksメソッドでページャを表示

orderByを使ってソート

#上記の例にorderByメソッドを追加するだけ
Hoge::orderBy('age', 'desc')->paginate(5);

参考URL

https://blog.hiroyuki90.com/articles/laravel-pagination/

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

laravelでseedを使っていてIntegrity constraint violation:1452エラーが表示された時

表題の件、今日だけで2回つまづいたので自分のためにも残しておきます!
laravelのseedを使っていて下記のエラーが出てしまった時は、外部キー元のテーブルに当該キーのデータがあるか確認しておきましょう。
無いとエラーになります。
灯台下暗しでした汗

SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テスト中にエラーが発生

こんなエラーが発生
vendorのadobeFontMetrics.phpの中でこれがダメだと

$tree = $kern["tree"];

結果的にlaravelのvendorのエラーだったぽい。
上記を削除して、下記を追加すれば良い。

$tree = is_array($kern) ? $kern["tree"] : null;

参考↓
https://github.com/PhenX/php-font-lib/pull/76/files

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

Laravel7でのRoute Model Bindingの新機能を使ってみた

Route Model Bindingとは

自分は暗黙の結合やルートモデル結合と良く言っています
今までのLaravelでのルートモデル結合ではidを指定する感じでした
下記のような感じです

ルートでこのように定義して
web.php
Route::get('/user/{user}', 'UserController@index');

コントローラーでこんな感じ

UserController.php
public function index(User $user)
{
    //
}

これでブラウザから/user/1などど叩くとUserモデルのidが1のモデルと勝手に結合してくれるものでした
これかLaravel7では新しくid以外のものを指定してできるようになってました

ルートモデル結合について詳しくはこちらをご覧ください→こちら

id以外のフィールドを指定する

id以外のフィールドを指定してルートモデル結合するにはこんな感じで書きます

web.php
Route::get('/user/{user:name}', 'UserController@index');

これで/user/{任意の名前}でURLを叩くことで
その{任意の名前}と一致するUserが結合して所得することができるようになっています

実際にサンプルを作ってみました

サンプルを作成する

まずはモデル、コントローラー、シーダ、マイグレーションを作成します

$ php artisan make:model Person --all

次にコントローラーですがリソースコントローラーが作成されていますが今回は使わず
別でコントローラーを作成します

$ php artisan make:controller PersonController

シーダファイルを編集します

PersonSeeder.php
<?php

use Illuminate\Database\Seeder;

class PersonSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $param = [
            'name' => '太郎'
        ];

        DB::table('people')->insert($param);

        $param = [
            'name' => '花子'
        ];

        DB::table('people')->insert($param);

        $param = [
            'name' => '次郎'
        ];

        DB::table('people')->insert($param);
    }
}

マイグレーションの編集をします

create_people_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePeopleTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('people', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('people');
    }
}

マイグレーション実行とシーダの実行をします

$ php artisan migrate
$ php artisan db:seed

これでDBとダミーデータの用意ができました
次にbladeファイルを作成します

resourcesmainフォルダを作成してindex.blade.phpを作成してください
index.blade.phpを下記のように編集しておきます

index.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ユーザー表示</title>
</head>
<body>
    {{ $person->name }}
</body>
</html>

web.phpを編集します
下記を追加

web.php
Route::get('/{person:name}', 'MainController@index');

コントローラーを変更します
MainController.phpに下記のアクションを追加してください

MainController.php
    public function index(Person $person)
    {
        return view('main.index', compact('person'));
    }

完了です

これでブラウザから/太郎としてみてください
ブラウザに太郎と表示されていると思います

今回はnameフィールドでバインドするように指定しているので
Personモデルに一致するnameをURLに指定してアクセスすると
一致したPersonの情報が所得できています

今までのようにidのみでなく
ルート定義の際にあんなに簡単にフィールドを指定してルートモデル結合できるのは
とても便利ですね

色々な使い方ができるなと思いました
使い方考えながら遊んでみようと思います

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

phpunitテスト

phpunit テストについて

laravelでテストをする
そもそもlaravelのテストは2種類用意されている。
1. デフォルトでは Feature と Unit というディレクトリに分かれていて、Feature には機能テスト、Unit にはユニットテストを書く構成になっている
2. 今回はFeatureテストについて

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

Laravel7でのCors対応について

Corsとは

ブラウザでは同一でのドメイン間でのアクセスしかできないようになっています
そのため、異なるドメインへのアクセス(APIなど)と通信することができないのですが
それを解決するために使用するのがCorsです

例えば、Laravel側でlocalhost:8000でNuxt側でlocalhost:3000で動かしていた場合
localhost:3000のNuxt側からaxiosなどを使ってlocalhost:8000のLaravel側のAPIにアクセスができません
それをクロスドメインアクセスを可能にする事ができます

Larvel7でのCors

ここからが本題何ですが、Cors対応しようと思い
今まで通りcomposerでインストールしようと思いconfigフォルダをみてみると
あれ?cors.phpがある!
という事に今頃気付きました。

今までやってた作業しなくてもLaravel7では必要ない?みたいですね

app/Http/Kernel.phpを見てみても$middleware\Fruitcake\Cors\HandleCors::classの記載もありました

cors.phpの中身をみてみる

cors.php
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Cross-Origin Resource Sharing (CORS) Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may configure your settings for cross-origin resource sharing
    | or "CORS". This determines what cross-origin operations may execute
    | in web browsers. You are free to adjust these settings as needed.
    |
    | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
    |
    */

    'paths' => ['api/*'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => false,

    'max_age' => false,

    'supports_credentials' => false,

];

apiからのアクセスは全て許可する設定になってるようで
これは今後LaravelではCors対応を手動でする必要がなくなったってことですかね?
とても助かりますね。。
と言うただ自分の中で発見があっただけの記事です

ついでにcors.phpの設定項目について自分のわかっている範囲で説明入れておきます

cors.phpの設定項目

cors.phpの各設定項目について少し触れておきます

cors.php
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Cross-Origin Resource Sharing (CORS) Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may configure your settings for cross-origin resource sharing
    | or "CORS". This determines what cross-origin operations may execute
    | in web browsers. You are free to adjust these settings as needed.
    |
    | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
    |
    */

    'paths' => ['api/*'],    //許可するパスの設定

    'allowed_methods' => ['*'],    //許可するメソッドの設定

    'allowed_origins' => ['*'],    //許可するドメインの設定

    'allowed_origins_patterns' => [],    //許可するドメインのパターン

    'allowed_headers' => ['*'],    //許可するヘッダーの設定

    'exposed_headers' => false,    //レスポンスヘッダーの公開指定

    'max_age' => false,    //ブラウザのキャッシュの保管期間

    'supports_credentials' => false,    //クッキーなどの認証の許可

];

[*]とすると全て許可することになります
少し不安点あるので間違っている点あれば、ご指摘頂きたいです。

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

LaravelのFormRequestの単体テストのやり方

ただバリデーションテストを書きたいだけだった

こんな感じでValidator取得すればいけるやろ〜

$formRequest = new FormRequest();

$v = $formRequest->getValidator();

$v->passes();

そこでValidatorを取得できそうなメソッドを探したが、すべてprotectedメソッドだったのであった...

こんなふうに書きました

public function test_FormRequest_validate()
{
    $data = [];

    $formRequest = new FormRequest();

    $v = \Validator::make(
        $data, $this->app->call([$formRequest, 'rules']),
        $formRequest->messages(), $formRequest->attributes()
    );

    $this->assertTrue($v->passes());
}

もとのFormRequestクラスの内部では↓こんなふうにValidatorを取得しています。

$factory->make(
    $this->validationData(), $this->container->call([$this, 'rules']),
    $this->messages(), $this->attributes()
);

app->call()とすることで、rules()でメソッドインジェクションをしていた場合でもDIしてくれます。

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

LaravelのFormRequestでバリデートまでのソースコードを読む

FormRequestでvalidateされるまで

FormRequestクラスの誕生

Illuminate/Routing/ControllerDispatcher.php
public function dispatch(Route $route, $controller, $method)
{
    $parameters = $this->resolveClassMethodDependencies(
        $route->parametersWithoutNulls(), $controller, $method
    );

    //...
}

resolveClassMethodDependencies()が怪しいので見ていきます。

Illuminate/Routing/RouteDependencyResolverTrait.php
protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
    //...

    return $this->resolveMethodDependencies(
        $parameters, new ReflectionMethod($instance, $method)
    );
}

resolveMethodDependencies()の返り値をreturnしているようです。

ならば見ていきましょう。

Illuminate/Routing/RouteDependencyResolverTrait.php
public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
{
    $instanceCount = 0;

    $values = array_values($parameters);

    foreach ($reflector->getParameters() as $key => $parameter) {
        $instance = $this->transformDependency(
            $parameter, $parameters
        );

        if (! is_null($instance)) {
            $instanceCount++;

            $this->spliceIntoParameters($parameters, $key, $instance);
        } elseif (! isset($values[$key - $instanceCount]) &&
                  $parameter->isDefaultValueAvailable()) {
            $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
        }
    }

    return $parameters;
}

まずparametersには基本的にルートモデルバインディングで解決されたものが入っています。

$parameters = ['user' => Userのインスタンス]

foreachのkeyとparameterの部分は、0 => ReflectionParameterクラスとなります。

ReflectionParameterクラス リファレンス

transforDependency()にReflectionParameterクラスと$parametersを渡しています。
今回は$parametersには何も入っていない体でいきます。

Illuminate/Routing/RouteDependencyResolverTrait.php
protected function transformDependency(ReflectionParameter $parameter, $parameters)
{
    $class = $parameter->getClass();

    if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
        return $parameter->isDefaultValueAvailable()
            ? $parameter->getDefaultValue()
            : $this->container->make($class->name);
    }
}

$parameter->getClass()でメソッドにタイプヒンティングしてあるクラス名を取得しています。
stringやintの場合はnullになります。

alreadyInParametersはその名の通り、すでにルートモデルバインディングで解決されたやつはtrueを返すやで〜ってやつなので今回は関係ないです。

return文のところは、デフォルト引数だとその値を使って、そうじゃなければクラス名からインスタンスを生成します。
(ここでFormRequestのインスタンスが生成されます)

そのままなんやかんやあってコントローラーメソッドに引き渡されていきます。

解決時の処理

FormRequestのインスタンスを生成するだけでバリデーションしてないじゃん!と思ったそこのあなた!

Laravelの魔法がここから始まります。

まずFormRequestはコンテナで解決されているので解決方法を見ていきましょう。そうサービスプロバイダーです。

Illuminate/Foundation/Providers/FormRequestServiceProvider.php
public function boot()
{
    $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) {
        $resolved->validateResolved();
    });

    $this->app->resolving(FormRequest::class, function ($request, $app) {
        $request = FormRequest::createFrom($app['request'], $request);

        $request->setContainer($app)->setRedirector($app->make(Redirector::class));
    });
}

resolving, afterResolvingは第一引数に渡されたクラスがコンテナで解決されたときに発行されるイベントをもとに実行されます。

さきほど見たようにFormRequestが生成された時点でresolvingで登録したコールバックが呼ばれます。

ここのresolvingはFormRequestにコンテナとリダイレクタとリクエストをセットしているぐらいです。

FormRequestはValidatesWhenResolvedインタフェースを実装しているので、afterResolvingのコールバックも呼ばれます。

ここでFormRequest#validateResolvedが呼ばれます。このメソッドはトレイとに実装されています。

Illuminate/Validation/ValidatesWhenResolvedTrait.php
public function validateResolved()
{
    $this->prepareForValidation();

    if (! $this->passesAuthorization()) {
        $this->failedAuthorization();
    }

    $instance = $this->getValidatorInstance();

    if ($instance->fails()) {
        $this->failedValidation($instance);
    }

    $this->passedValidation();
}

prepareForValidation()はデフォルトではなにもしないメソッドになってます。
バリデーションの前になにかしたいことがあれば、このメソッドをオーバーライドすればよさそうです。

getValidatorInstance()でバリデータを取得していそうです。

Illuminate/Foundation/Http/FormRequest.php
protected function getValidatorInstance()
{
    //...

    $factory = $this->container->make(ValidationFactory::class);

    if (method_exists($this, 'validator')) {
        $validator = $this->container->call([$this, 'validator'], compact('factory'));
    } else {
        $validator = $this->createDefaultValidator($factory);
    }

    //...

    $this->setValidator($validator);

    return $this->validator;
}

FormRequestにはvalidator()がないので、$this->createDefaultValidator($factory)が呼ばれます。

Illuminate/Foundation/Http/FormRequest.php
protected function createDefaultValidator(ValidationFactory $factory)
{
    return $factory->make(
        $this->validationData(), $this->container->call([$this, 'rules']),
        $this->messages(), $this->attributes()
    );
}

これでIlluminate\Contracts\Validation\Validator.phpを作成して、プロパティにセットしつつreturnします。

Illuminate/Validation/ValidatesWhenResolvedTrait.php
public function validateResolved()
{
    $this->prepareForValidation();

    if (! $this->passesAuthorization()) {
        $this->failedAuthorization();
    }

    $instance = $this->getValidatorInstance();

    if ($instance->fails()) {
        $this->failedValidation($instance);
    }

    $this->passedValidation();
}

$instance->fails()でバリデーションを行って、バリデーションに引っかかればエラーが投げられます。

$this->passedValidation()はなにもしないメソッドなので、バリデーション後にしたい処理をオーバーライドして記述すればよいでしょう。

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

LaravelでTDDを始めよう

LaravelでのUnitTestを始めよう

TestUnitを始める上で勢いで始めないように設計しましょう。

・基本的に読むべきこと
https://phpunit.readthedocs.io/ja/latest/
https://readouble.com/laravel/5.5/ja/testing.html

アンチパターンとベストプラクティス
https://www.ryuzee.com/contents/blog/3982

  1. テストの中で何もテストしていない
    書きかけの場合は、テストに未完成の印をつけるなどしましょう
    https://phpunit.readthedocs.io/ja/latest/incomplete-and-skipped-tests.html

  2. 1つのテストメソッドの中で色々テストし過ぎている

  3. 引数に真にしたい条件を与えてなんでもassertTrueでチェックしている
    @dependsアノテーションを使ってテストの実行順序を制御しつつ分割を行うべきです。

  4. テストデータのパターンが複数あるのを理由に1つのテストの中で順番に検証する
    引数は検証対象の値やオブジェクトであるべきで、テストの意図を伝わりやすくすべきです。

  5. テストが分類されていない。適切なフォルダ構成になっていない
    解決策は適切にフォルダに分けて配置したり、テストスイートをつくったり、@groupアノテーションを使ってテストを分類します。

LaravelでPhpUnitを使う
LaravelでPHPUnitを使用する際、デフォルトの状態であればtests/TestCase.phpが作成されており、全てのテストクラスはこれを継承してテストを行います。

参考:https://memorandumrail.com/laravel-phpunit/
LaravelではデフォルでhFeatureとUnitというディレクトリに分かれていて、Feature には機能テスト、Unit にはユニットテストを書く構成になっています。
関数単位、クラス単位の場合はUnit

Mockeryを使うと疑似モックを作ってくれるので便利らしい。

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

Laravelでイベントとリスナーを使ってみる

イベントとは

プログラムから何らかの操作があった際などに処理を実行する仕組です
イベントを発行することでイベントクラスを呼び出すことができ
イベント自体に処理はなく、イベントでは主に値を扱いその値をリスナーへと受け渡します

リスナーとは

発生したイベントを受け取りイベントの発生を監視しているのがリスナー
リスナーで実際に処理が行われ
リスナーはイベント毎に作成し登録する必要があります

イベントを利用する方法

イベントを利用するには、イベントクラスとそれに対応するリスナークラスを作成すし登録する必要があり
そして、必要に応じてイベントを発行すればそのイベントを受け取るイベントリスナーの処理が実行される仕組みになります

イベントとリスナーを作成する

イベントクラスとイベントリスナークラスを作成するには
providersEventServiceProviderを利用します
EventServiceProvider.phpには$listenboot()メソッドが用意されています
イベントクラスとイベントリスナークラスの作成には$listenを使用します

EventServiceProvider.php
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        'App\Events\ItemEvent' => [
            'App\Listeners\ItemEventLister'
        ]
    ];

今回はこのように記述しておきます
次に、下記コマンドを実行します

$ php artisan event:generate

このコマンドの実行により、$lintenに登録したイベントクラスとイベントリスナークラスが自動的に作成されます
イベントクラスはapp/Eventsファオルだに作成され
イベントリスナークラスはapp\Listnersフォルダに作成されます

それでは実際に作成された2つのファイルの構成を見ていきます

イベントクラスの構成

ItemEventクラスは下記のようになっています

ItemEvent.php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ItemEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct()
    {
       //
    }

    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

イベントクラス内には様々なトレイトが用意されているのと
またメソッドは__construct()broadcastOn()の二つのメソッドが用意されていますが
基本的には、イベントは値を扱うのがメインになりますので__construct()以外はほとんと使用しません
忘れてはいけないのは、イベントクラスは値を扱うのが仕事なので処理などは必要ないです

次に、イベントリスナークラスの構成を見てみます

イベントリスナークラスの構成

ItemEventListerクラスは下記のようになっています

ItemEventLister.php
use App\Events\ItemEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class ItemEventLister
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  ItemEvent  $event
     * @return void
     */
    public function handle(ItemEvent $event)
    {
        //
    }
}

イベントリスナークラスにはhandle()メソッドと__construct()の二つが用意されています
リスナーは処理をする役割があるので、処理を記述するのがhandle()メソッドになります
引数にはイベントクラスのインスタンスが渡されており、この引数からイベントで管理していた値にアクセスすることができます
また、このhandle()メソッドが、イベント発生時に呼び出されます

次にイベントを発行するにはどうすれば良いのかです

イベントの発行

イベントを発行するのは簡単で下記のようにすることでイベントを発行できます

event(new ItemEvent(値));

ここまでで、イベントクラスとイベントリスナークラスの作成とイベントの発行方法など基本を説明しました
次にこれからを使って実際にイベント発行から値を受け取り、リスナーで処理を実行してみるサンプルを作成してみます

サンプル機能の作成

前での説明で使用した際に作成したItemEventクラスとItemEventListerクラスを引き続き使っていきます
まずは今回のサンプルで使用するItemモデル、シーダ、マイグレーション、コントローラーを作成していきます

$ php artisan make:model Item --all

これでモデル、シーダ、マイグレーション、コントローラーが作成できました
最初にマイグレーションの記述を行います
下記のようにしてください

create_items_talbe.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('items', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('items');
    }
}

マイグレーション実行します

$ php artisan migrate

次にシーダファイルを下記のように修正

ItemSeeder.php
<?php

use Illuminate\Database\Seeder;

class ItemSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $param = [
            'name' => 'りんご'
        ];

        DB::table('items')->insert($param);

        $param = [
            'name' => 'ばなな'
        ];

        DB::table('items')->insert($param);

        $param = [
            'name' => 'ぶどう'
        ];

        DB::table('items')->insert($param);
    }
}

シーダを実行します

$ php artisan db:seed

これでitemsテーブルに3件おダミーデータが用意できたかと思います

次にコントローラーを作成します
ここでイベントを使用します
ItemControllerが作成されていると思いますがリソースコントローラーなので今回は使わずに新たに作成します

$ php artisan make:controller MainController

MainControllerを修正していきます

MainController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Item;    //Itemモデルをuse
use App\Events\ItemEvent;    //ItemEventクラスをuse

class MainController extends Controller
{
    public function index()
    {
        return view('main.index');
    }

    public function item(Item $item)
    {
        event(new ItemEvent($item));    //イベント発行

        return redirect()->route('index');
    }
}

上記のコントローラーのitem()アクションでイベントを発行する処理をしています

次にルートの定義をしていきます

web.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/item}', 'MainController@item')->name('item');
Route::get('/', 'MainController@index')->name('index');

2つ追加できました

次にresourcesmainフォルダを作成しindex.blade.phpを作成してください
中身はこんな感じにしておきます

index.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ユーザー表示</title>
</head>
<body>
    イベントとリスナー
</body>
</html>

ここまでで必要な準備が終わりです
イベントも発行してるのであとはそのイベントの処理を用意するだけになります

ItemEventを修正していきます

ItemEvent.php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ItemEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $item;

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

    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

public $itemとしてItemのインスタンスを保管しておくプロパティを用意しています
__constructで引数で渡された$itemを利用してます
この引数の$itemMainControlleritem()アクションでイベント発行してる際に引数で渡されている値です
この部分ですevent(new ItemEvent($item))
ここで受け取ったItemの情報をイベントに渡してイベント側でそれを値として扱っています

次にイベントリスナークラスの修正をします

ItemEventLister.php
<?php

namespace App\Listeners;

use App\Events\ItemEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Storage;

class ItemEventLister
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  ItemEvent  $event
     * @return void
     */
    public function handle(ItemEvent $event)
    {
        Storage::append('item_accesslog.txt', '[Item]' . now() . '' . $event->item->name);
    }
}

簡単に説明すると、item_accesslog.txtというファイルに[item]+ 現在の時間 + ユーザーの名前の形で
書き出すということを処理しているリスナーになります
ちなみに、item_accesslog.txtファイルは作ってませんがそのファイルがなければ自動的にstorage/appに作成されます

これでサンプルの実装は完了です
ブラウザから/1とURLを叩いてみてください
そうすると、storage/app/item_accesslog.txt[Item]現在の時間 りんごと書き出しされているかと思います

一連の流れを簡単に説明すると

  • URLで指定された番号のItemの情報がMainControlleritem()アクションの引数で渡される
  • item()アクションの中のイベント発行の際にその値を渡してあげる
  • その値を受け取ったイベントクラスであるItemEventがその値を__constructを使って保管している
  • そのイベント発行により呼び出されたイベントリスナーItemEventListerhandle()メソッドが処理を実行してファイルに書き出しを行っている

こんな感じです
これが一連のイベント作成からイベント発行しリスナーに処理をさせるまでになります

またイベントをキューに登録して実行させることもできます

キューを利用する

発行したイベントをキューテーブルに登録し処理したい場合は
イベントリスナークラスにてインターフェースを実装するだけで利用ができます
今回の場合だとItemEventListerクラスになります

namespace App\Listner;

class ItemEventLister implements ShouldQueue
{
    //省略
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

aws-sdk-php-laravelを利用してファイルアップロード

aws-sdk-php-laravelとは

AWS SDK for PHPをlaravelのために使いやすくしたもの

(そもそもAWS SDKとはawsのサービスを使いやすくするソフトウェア開発キットのこと)

インストール

aws-sdk-php-laravelをインストールする

composer.jsonに書く

compsoerを通じてインストールします

composer.json
{
    "require": {
        "aws/aws-sdk-php-laravel": "~3.0"
    }
}
composer update

conposer updateを実行する

$ composer update
config/app.php

config/app.phpにAWS Service Providerを追加します

'providers'と下の方にある'aliases'に下のように追加してください

config/app.php
  'providers' => array(
        // ...
        Aws\Laravel\AwsServiceProvider::class,
    )

'aliases' => array(
        // ...
        'AWS' => Aws\Laravel\AwsFacade::class,
    )

設定

環境変数

デフォルトでは、これらの環境変数が使われます。

.env
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_REGION (default = us-east-1)

環境変数の設定方法はこれをみてもらったら早いと思います。
https://qiita.com/tiwu_official/items/ecb115a92ebfebf6a92f

AWS_DEFAULT_REGION名前はAWS_REGIONに変更します。

aws.config

.envで設定した環境変数はこんな感じで使われてるよーって

aws.config
return [
    'credentials' => [
        'key'    => env('AWS_ACCESS_KEY_ID', ''),
        'secret' => env('AWS_SECRET_ACCESS_KEY', ''),
    ],
    'region' => env('AWS_REGION', 'us-east-1'),
    'version' => 'latest',

    // You can override settings for specific services
    'Ses' => [
        'region' => 'us-east-1',
    ],
];

使い方

画像をアップロード
$s3 = App::make('aws')->createClient('s3');
$s3->putObject(array(
    'Bucket'     => 'YOUR_BUCKET', #s3で登録したbucketの名前
    'Key'        => 'YOUR_OBJECT_KEY', #登録したいファイルの名前
    'SourceFile' => '/the/path/to/the/file/you/are/uploading.ext', #登録したいファイル
));

これで画像をアップロードすることができる。

画像を表示
$s3 = App::make('aws')->createClient('s3');

$key = 'YOUR_OBJECT_KEY'; #取得したいファイルの名前
$bucket = env('AWS_BUCKET'); #bucketの名前

$image = $s3->getObjectUrl($bucket, $key); #getObjectUrlでurlを取得

これでviewに表示できます。

参考

公式のgithub(ほぼこれの日本語訳です)
https://github.com/aws/aws-sdk-php-laravel/blob/master/README.md

超簡単!LaravelでS3を利用する手順
https://qiita.com/tiwu_official/items/ecb115a92ebfebf6a92f

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

Mac Laravelのライブラリ「laravel-dompdf」を日本語表示に対応させる

目的

  • Laravelにライブラリ「laravel-dompdf」を入れたが、日本語化されない問題があったため改善する

実施環境

  • ハードウェア環境
項目 情報 備考
OS macOS Catalina(10.15.3)
ハードウェア MacBook Air (11-inch ,2012)
プロセッサ 1.7 GHz デュアルコアIntel Core i5
メモリ 8 GB 1600 MHz DDR3
グラフィックス Intel HD Graphics 4000 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3
Laravel バージョン 7.0.8
  • ライブラリ環境
項目 情報 備考
laravel-dompdf v0.8.6 アプリ名ディレクトリに移動後コマンド`composer show -i

実施条件

実施方法概要

  • Bladeファイルを利用した日本語化対応の方法をまとめる。
  1. 準備
  2. フォントのファイルの取得
  3. ライブラリに登録するためのスクリプト取得
  4. ライブラリに登録
  5. 確認

実施方法詳細

  1. 準備

    1. 下記コマンドを実行してフォントのファイルを設置するディレクトリを作成する

      $ mkdir アプリ名ディレクトリ/fonts
      
  2. フォントのファイルの取得

    1. 下記リンク先にアクセスする。

      IPAフォント ダウンロードページ

    2. リンク先の「TTFファイル」の「4書体パック」のzipをクリックしてフォントをインストールする。

      laravel_dompdf_japanese_md_と_IPAフォントのダウンロード.png

    3. お使いのMacのダウンロードフォルダにダウンロードされたフォントのzipファイル「IPAfontXXXXX.zip」を解凍する。(Xには任意の数字)

    4. 下記コマンドを実行して、解凍した「IPAfontXXXXX」の中のファイル郡を先に作成したアプリ名ディレクトリ/fontsにコピーする。

      $ cd アプリ名ディレクトリ
      $ cp ~/Downloads/IPAfont00303/* fonts/
      
    5. フォントのファイルを設置したアプリ名ディレクトリ/fonts直下の状態を下記に記載する。

      • ~アプリ名ルートフォルダ/fonts
        • IPA_Font_License_Agreement_vX.X.txt
        • Readme_IPAfontXXXXX.txt
        • ipag.ttf
        • ipagp.ttf
        • ipam.ttf
        • ipamp.ttf
  3. ライブラリに登録するためのスクリプト取得

    1. 下記のリンク先にアクセスする。
    2. 「Clone or download」をクリックする。

      dompdf_utils__Utility_scripts_for_use_with_the_dompdf_library.png

    3. 「Download ZIP」をクリックする。

      dompdf_utils__Utility_scripts_for_use_with_the_dompdf_library_と_laravel_dompdf_japanese_md.png

    4. お使いのMacのダウンロードフォルダにダウンロードされたzipファイル「utils-master.zip」を解凍する。

    5. 下記コマンドを実行して解凍したフォルダ「utils-master」の中の「load_font.php」をアプリ名ディレクトリ直下にコピーする。

      $ cd アプリ名ディレクトリ
      $ cp ~/Downloads/utils-master/load_font.php .
      
    6. アプリ名ディレクトリ直下に「load_font.php」があることを確認する。

  4. ライブラリに登録

    1. 下記コマンドを実行する。(筆者環境での下記コマンドの実行後の出力を付録に記載する。)

      $ php load_font.php ipag fonts/ipag.ttf
      
    2. 下記コマンドをアプリ名ディレクトリで実行してフォントの設定ファイルを確認する。

      $ cd アプリ名ディレクトリ
      $ vi vendor/dompdf/dompdf/lib/fonts/dompdf_font_family_cache.php
      
    3. 先のコマンドを実行して開いたファイルに下記の記載がある事を確認する。

      vendor/dompdf/dompdf/lib/fonts/dompdf_font_family_cache.php
      'ipag' => array(
      'normal' => $fontDir . '/ipag',
      'bold' => $fontDir . '/ipag',
      'italic' => $fontDir . '/ipag',
      'bold_italic' => $fontDir . '/ipag',
      ),
      
    4. 下記コマンドを実行してスクリプトファイルにより作成されたライブラリが読むためのフォントデータをコピーする。

      $ cd アプリ名ディレクトリ
      $ cp -r vendor/dompdf/dompdf/lib/fonts storage/
      
  5. 確認

    1. 下記コマンドを実行してアプリ名ディレクトリ/resource/viewに「test」フォルダを作成する。

      $ cd アプリ名ディレクトリ
      $ mkdir resources/views/test
      
    2. 下記コマンドを実行してTestコントローラを作成する。

      $ cd アプリ名ディレクトリ
      $ php artisan make:controller TestController
      >Controller created successfully.
      
    3. 下記コマンドを実行して先に作成したコントローラを開く。

      $ cd アプリ名ディレクトリ
      $ vi app/Http/Controllers/TestController.php
      
    4. 先のコマンドで開いたコントローラをの内容を下記の様に修正する。

      アプリ名ディレクトリ/app/Http/Controllers/TestController.php
      <?php
      
      namespace App\Http\Controllers;
      
      use Illuminate\Http\Request;
      
      use PDF;
      
      class TestController extends Controller
      {
          public function japanese_pdf_test() {
              $pdf = PDF::loadView('test.japanese_pdf_test');
              return $pdf->stream();
          }
      }
      
    5. 下記の記載をアプリ名ディレクトリ/routesにあるweb.phpのルーティングファイルに追記する。

      アプリ名ディレクトリ/routes/web.php
      Route::get('/japanese_pdf_test', 'TestController@japanese_pdf_test');
      
    6. 下記コマンドを実行してビューファイルを作成し開く。

      $ cd アプリ名ディレクトリ
      $ vi resources/views/test/japanese_pdf_test.blade.php
      
    7. 開いたビューファイルに下記の内容を記載する。

      アプリ名ディレクトリ/resources/views/test/japanese_pdf_test.blade.php
      <!doctype html>
      <html>
        <head>
          <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
          <title>PDF</title>
          <style>
          body {
          font-family: ipag;
          }
          </style>
        </head>
        <body>
            <h1>こんにちは</h1>
        </div>
        </body>
      </html>
      
    8. 下記コマンドを実行してローカルサーバを起動する。

      $ cd アプリ名ディレクトリ
      $ php artisan serve
      
    9. 下記のリンク先にアクセスしブラウザから文字化けせずに「こんにちは」とPDFとして出力されることを確認する。

付録

  • コマンドphp load_font.php ipag storage/fonts/ipag.ttfを実行した際の筆者の環境でのターミナル出力を下記に記載する。

    $ php load_font.php ipag fonts/ipag.ttf
    >Unable to find bold face file.
    >Unable to find italic face file.
    >Unable to find bold_italic face file.
    >Copying fonts/ipag.ttf to /Users/shun/workspace/study/laravel/calculation_drill_app/vendor/dompdf/dompdf/lib/fonts/ipag.ttf...
    >Generating Adobe Font Metrics for /Users/shun/workspace/study/laravel/calculation_drill_app/vendor/dompdf/dompdf/lib/fonts/ipag...
    >PHP Notice:  Trying to access array offset on value of type null in /Users/shun/workspace/study/laravel/calculation_drill_app/vendor/phenx/php-font-lib/src/FontLib/AdobeFontMetrics.php on line 142
    >PHP Stack trace:
    >PHP   1. {main}() /Users/shun/workspace/study/laravel/calculation_drill_app/load_font.php:0
    >PHP   2. install_font_family() /Users/shun/workspace/study/laravel/calculation_drill_app/load_font.php:201
    >PHP   3. FontLib\TrueType\File->saveAdobeFontMetrics() /Users/shun/workspace/study/laravel/calculation_drill_app/load_font.php:155
    >PHP   4. FontLib\AdobeFontMetrics->write() /Users/shun/workspace/study/laravel/calculation_drill_app/vendor/phenx/php-font-lib/src/FontLib/TrueType/File.php:361
    >
    >Notice: Trying to access array offset on value of type null in /Users/shun/workspace/study/laravel/calculation_drill_app/vendor/phenx/php-font-lib/src/FontLib/AdobeFontMetrics.php on line 142
    >
    >Call Stack:
    >    0.0086     423096   1. {main}() /Users/shun/workspace/study/laravel/calculation_drill_app/load_font.php:0
    >    0.0484    4914016   2. install_font_family() /Users/shun/workspace/study/laravel/calculation_drill_app/load_font.php:201
    >    0.0858    5068992   3. FontLib\TrueType\File->saveAdobeFontMetrics() /Users/shun/workspace/study/laravel/calculation_drill_app/load_font.php:155
    >    0.0862    5103120   4. FontLib\AdobeFontMetrics->write() /Users/shun/workspace/study/laravel/calculation_drill_app/vendor/phenx/php-font-lib/src/FontLib/TrueType/File.php:361
    

参考文献

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

Laravel7+Reactの環境を構築する

Laravel7 で React を利用した開発を可能にする手順をご紹介いたします。
ここでは Laravel と React はインストール済みとして進めていきます。

開発環境

  • Laravel 7.2.1
  • React 16.13.1
  • PHP 7.4.3

Laravel で React を利用できるようにする

React を利用する Laravel のプロジェクトを作成し、
作成されたプロジェクトフォルダに遷移します。

composer create-project laravel/laravel hoge-project
cd hoge-project

次に Laravel が React を利用するように設定を変更します。
ui artisan コマンドを使用するので、laravel/ui パッケージを先にインストールしています。

composer require laravel/ui
php artisan ui react

最後に下記コマンドを実行すると、Laravel で React を使えるようになります。

npm install && npm run dev

Laravel の動作確認をする

Laravel が正常に動作するか確認します。

php artisan serve

http://127.0.0.1:8000 にアクセスして、Laravel のページが表示されたら正常に動作しています。

スクリーンショット 2020-03-26 7.27.19.png

js ファイルの変更を常時反映させるようにしておく

現状のままだと js ファイルの変更が検知されないので、常時変更を検知してくれるように設定します。
別のターミナルを起動し、Laravel のプロジェクトフォルダ内で npm run watch を実行します。

cd hoge-project
npm run watch

React のサンプルファイルで文字列を表示する

React を利用して画面上に文字列を表示できるよう、既存ファイルを変更していきます。
まずは React を使用しているサンプルファイル(resources\js\components\Example.js)を見てみましょう。

resources\js\components\Example.js
import React from 'react';
import ReactDOM from 'react-dom';

function Example() {
    return (
        // 省略
    );
}

export default Example;

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

id が "example" の HTML タグに文字列を表示する処理だとわかります。

それでは Example.js が出力する内容を表示する HTML タグを記述しましょう。
http://127.0.0.1:8000 にアクセスすると表示される内容は welcome.blade.php(resources\views\welcome.blade.php)に記述されているので、
下記のように編集します。

resouces/views/welcome.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
    </head>
    <body>
        <div id="example"></div>
        <script src="{{ mix('js/app.js') }}"></script>
    </body>
</html>

body タグ内に、React 出力データの挿入場所である <div id="example"></div> と、
Example.js を処理するファイル app.js の読み込みを記述します。

http://127.0.0.1:8000 にアクセスし、下記の通りに表示されていれば成功です。

スクリーンショット 2020-03-26 8.02.25.png

もし Example.js 以外のファイル、例えば Hoge.js を読みこませたい場合は、
app.js(resources\js\app.js)を下記のように編集すると良いでしょう。

\resources\js\app.js
require('./bootstrap');
require('./components/Example');

// 下記を追記
require('./components/Hoge');

以上です。

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

【Laravel】テストのsetUp()でエラー「Declaration of…must be compatible with...」

状況

UserTest.php
class UserTest extends TestCase
{
    public function setUp()
    {
       //略
    }
}

laravelのphpunitのsetUp()メソッドを上記のように書き、テストを行ったところ
次のようなエラーが出ました。

PHP Fatal error:  Declaration of Tests\Feature\UserTest::setUp() must be compatible with Illuminate\Foundation\Testing\TestCase::setUp(): void in /var/www/tests/Feature/Usertest.php on line 13

エラー内容とその対処

要はテストのsetUp()とIlluminate\Foundation\Testing\TestCase
のsetUp()の返り値が違うということを言われているわけです。

そこでIlluminate\Foundation\Testing\TestCaseクラスを見てみます。

TestCase.php
    protected function setUp(): void
    {
        //略
    }

Illuminate\Foundation\Testing\TestCaseクラスではsetUp()にvoidが返り値として宣言されています。

したがってテストのほうにもvoidのタイプヒンディングをしましょう。

UserTest.php
class UserTest extends TestCase
{
    public function setUp() :void
    {
       //略
    }
}

これで完了。
setUp()が使えるようになると思います。

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

Laravel-admin で アバターを非表示にする

やりたいこと

Laravel-adminをいれるとデフォルトで管理者ユーザーにアバターが振られていて
しかも、管理者編集から画像を消そうにもデフォルトでセットされる

スクリーンショット 2020-03-26 0.25.09.png

うざいので消します

やりかた

下記のように管理者ユーザーテーブルのアバターはnullを許可されているので問題有りません

 Schema::create(config('admin.database.users_table'), function (Blueprint $table) {
            $table->increments('id');
            $table->string('username', 190)->unique();
            $table->string('password', 60);
            $table->string('name');
            $table->string('avatar')->nullable();
            $table->string('remember_token', 100)->nullable();
            $table->timestamps();
        });

次に、Administratorという管理者のモデルをみると
下記のようにavatarのアクセサが定義されているのですが
そこでnullだとlaravel-admin側で用意した画像を使うようになっています

vendor/encore/laravel-admin/src/Auth/Database/Administrator.php
    /**
     * Get avatar attribute.
     *
     * @param string $avatar
     *
     * @return string
     */
    public function getAvatarAttribute($avatar)
    {
        if (url()->isValidUrl($avatar)) {
            return $avatar;
        }

        $disk = config('admin.upload.disk');

        if ($avatar && array_key_exists($disk, config('filesystems.disks'))) {
            return Storage::disk(config('admin.upload.disk'))->url($avatar);
        }

        $default = config('admin.default_avatar') ?: '/vendor/laravel-admin/AdminLTE/dist/img/user2-160x160.jpg';

        return admin_asset($default);
    }

なのでこいつを編集してやります
app/Admin/Models/Administrator.phpを作成
中身をコピーし、下記のように書き換えます。また、一つgetPlainAvatarAttributeというアクセサを追加してやります。

    public function getPlainAvatarAttribute()
    {
        return $this->getOriginal("avatar");;
    }

    /**
     * Get avatar attribute.
     *
     * @param string $avatar
     *
     * @return string
     */
    public function getAvatarAttribute($avatar)
    {
        if (URL::isValidUrl($avatar)) {
            return $avatar;
        }

        $disk = config('admin.upload.disk');

        if ($avatar && array_key_exists($disk, config('filesystems.disks'))) {
            return Storage::disk(config('admin.upload.disk'))->url($avatar);
        }

        $default = config('admin.default_avatar') ?: null;

        return admin_asset($default);
    }

その後admin.php
Encore\Admin\Auth\Database\Administrator::class,となっているところを
App\Admin\Models\Administrator::class,に変更します

これだけだと、画面で画像ありませんマークがでてしまうので
view側もいじります。

vendor/encore/laravel-admin/resources/viewsを丸々コピーし
resources/views/laravel-adminというディレクトリを作成し格納します。

app/Admin/bootstrap.phpに下記の一文を追加し、viewの参照を変更。

app/Admin/bootstrap.php
app('view')->prependNamespace('admin', resource_path('views/laravel-admin'));

sidebarとheaderの修正をします

@if(Admin::user()->plainAvatar)
<img src="{{ Admin::user()->avatar }}" class="img-circle" alt="User Image">
@endif

これで、きえました

スクリーンショット 2020-03-26 0.43.26.png

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