20200522のlaravelに関する記事は13件です。

XAMPPでLaravel開発環境構築

XAMPPを使って、Laravel開発環境を構築します。

以下のサイトから、Windows向けのXAMPPをダウンロードします。
https://www.apachefriends.org/jp/download.html
私の環境では、[PHP 7.3.18]を使用しました。
ダブルクリックしてインストールを行います。
このとき、からパスを変更しないほうが良いです。

インストールが完了したら、

を起動しましょう。

XAMPP Control Panelが立ち上がったら、さっそくApacheを起動してみます。
Apacheの[Start]ボタンをクリックしてみましょう。
次に、[Admin]ボタンをクリックすると、ブラウザが立ち上がります。
Skypeを入れていると、エラーが発生し、立ち上がらないことがあります。
Skypeが80、443ポートを使用しているために、既に使われているとエラーが発生し立ち上がらないことがあります。
この解決策は、Apacheの[Config]ボタンをクリックして、[Apache(httpd.conf)]の修正が必要です。

エディターで起動してみましょう。
修正箇所は2か所です。
①Listen 80
②ServerName localhost:80
ポートを80以外に変更します。
今回は8081に変更しました。

では、再度[Start]して[Admin]から、起動を確認しましょう。
ブラウザが立ち上がり、エラーとなりました。
URLをhttp://localhost/ → http://localhost:8081/に変更して確認したところ
正常に立ち上がっていることを確認しました。

では、Laravelでプロジェクトを構築します。
コマンドプロンプトを開いてください。
[cd ]と入力し、ルートに移動します。
次に[cd xampp\htdocs]に移動します。

composerを使用して、プロジェクトを作成します。
composer create-project laravel/laravel --prefer-dist {作成するプロジェクト名}
と入力してEnterしてください。
5分程度かかりますので、そのままお待ちください。
Successと表示されたら、成功です。

次に、エクスプローラからをエディターで開きます。
DocumentRootとDirectoryを設定します。

DocumentRoot "/xampp/htdocs/{作成済プロジェクト名}/public"

保存して、同様にxampp Control Panelから[Admin]を起動してみましょう。
Laravelの画面が表示されたらOKです。

次は、XAMPP Control PANELからMySQLのAdminボタンをクリックして、MySQLを起動します。
ポートを変更していますので、エラーが出るようでしたら、
http://localhost:8081/phpmyadmin/
と入力しなおしてください。(ポートはご自身の環境に合わせて)

左側のツリーから新規作成ボタンをクリックしてデータベースを作成します。
DB名称は作成済のプロジェクトに合わせて名称を入力します。
文字コードは[utf8-general-ci]を選択しました。
最後に作成ボタンをクリックして終了です。

.envファイルの設定も必要です。
エクスプローラからに移動します。
その下に保存されている<.env>ファイルをエディターで開きます。
そして、以下の設定を変更します。
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306 ←MySQLのデフォルト
DB_DATABASE=作成したデータベース名
DB_USERNAME=root ←デフォルト
DB_PASSWORD= ←デフォルト

次に1行目のAPP_NAMEも設定してください。

デフォルトの表示言語を変更します。
初期は英語で表示されていますので、日本語に設定する際は、

をエディターで開き、以下の2か所を修正します。
 ① 'locale' => 'en', →  'locale' => 'ja',
 ② 'timezone' => 'Asia/Tokyo',

では、起動を確認してみましょう。
Apacheを一度[Stop]してから[Start]して[Admin]から確認をしてください。
Laravelが表示されれば、成功です。

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

PHP LaravelをIISで稼働させる

PHP LaravelをIISで構築します。

OS:Windows10 Pro
Webサーバー:IIS10

IIS10でPHPを稼働させるには、「CGI」を有効にする必要があります。
「コントロールパネル」から「プログラムと機能」を選択します。
「Windowsの機能の有効化または無効化」を選択して表示されたウインドウから
「インターネットインフォメーションサービス」を選択して、
「World Wide Webサービス」-「アプリケーション開発機能」-「CGI」にチェックを入れて「OK」してください。

インストールが完了したら、IISマネージャーを立ち上げてください。
https://windows.php.net/download/
からNon Thread SafeのZIPファイルをダウンロードします。

ZIPを解凍したら、[php.ini-production]を[php.ini]にリネームしてコピーしてください。

php.iniファイルをエディタで開いて、以下の設定を追加・変更します。

fastcgi.impersonate = 1
fastcgi.logging = 0
cgi.fix_pathinfo = 1
cgi.force_redirect = 0

展開したフォルダ内にある、[php-cgi.exe]を実行してください。

エラーが出ていないことを確認してくださいね。

次に、ACLを設定します。
PHPフォルダにデフォルトのIIS起動ユーザである「IIS_IUSRS」にアクセス権を追加します。
アクセス権はデフォルトのまま、変更の必要はありません。

次に、PHPランタイムを登録します。
IISマネージャーを起動して、左側の接続ペインからコンピュータ名をクリックします。
真ん中のビューの下側に、機能ビューとコンテンツビューが表示されていますが、
そのうち、機能ビューを選択して、「ハンドラーマッピング」をダブルクリックします。
右側の操作ペインから「モジュールマップの追加」を選択して、以下の内容を登録してください。
・要求パス  *.php
・モジュール FastCgiModule
・実行可能ファイル PHPのインストールパス\php-cgi.exe
・名前     PHP

「要求の制御」ボタンをクリックして、マップタブの「要求のマップ先が次の場合のみハンドラーを呼び出す」に
チェックをいれて、「ファイルまたはフォルダ」を選択して「OK」します。

動作確認
にinfo.phpファイルを作成します。
エディタで開いて、以下を入力してください。
<?php phpinfo(); ?>

ブラウザを開いて、
http://localhost/info.phpにアクセスしてPHP情報が表示されていることを確認できれば完了です。

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

Laravelでseederをsubdirectoryに設置したい時

Laravel 5.8

Seederの中でディレクトリを切りたい時。単にディレクトリを切るだけだとダメ。
composer.jsonのclassmapに追加する。

composer.json
"autoload": {
    "classmap": [
        "app/commands",
        "app/controllers",
        "app/models",
        "app/database/migrations",
        "app/database/seeds",
        "app/tests/TestCase.php",
        "app/database/seeds/SubDirectory"  // <-- 追加
    ]
}

ついかしたら下記を実行

composer dump-autoload

参考
https://stackoverflow.com/questions/23919825/call-database-seeder-from-a-subfolder

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

CircleCIについて

前提

本日学んだことを書いていきます。

本題

CI(継続的インテグレーション)

変更されたソースコードがGitHubなどのリポジトリに提出(プッシュ)されるたびに自動でテストを行うことで、変更後のソースコードをリポジトリのメインライン(masterブランチなど)に統合(gitで言うところのマージ)可能な状態にする仕組み

CD(継続的デリバリー)

メインラインのソースコードが変更されるたびに、自動で本番環境などにデプロイする仕組み

RefreshDatabase

TestCaseクラスを継承したクラスでRefreshDatabaseトレイトを使用すると、データベースをリセットする

なお、トレイトは継承と似たPHPの機能で、汎用性の高いメソッドなどをトレイトとしてまとめておき、他の複数のクラスで共通して使う、という使い方をする

テストのメソッド名

Laravelでは、テストにPHPUnitというテストフレームワークを使用する
PHPUnitでは、テストのメソッド名の先頭にtestを付ける必要がある
ここではindexメソッドのテストであるとわかるよう、testIndexという名前にすることにした

public function testIndex()

※メソッド名をtest始まりにしたくない場合は、以下の例のようにメソッドのドキュメントに@testと記述する

/**
 * @test
 */
public function initialBalanceShouldBe0()
{
    $this->assertSame(0, $this->ba->getBalance());
}

assertRedirect
引数として渡したURLにリダイレクトされたかどうかをテストする

ArticleControllerTest.php
$response->assertRedirect(route('login'));

route('login')は、ログイン画面のURLを返す

factory関数とcreateメソッド
factory関数を使用することで、テストに必要なモデルのインスタンスを、ファクトリというものを利用して生成できる
factory(User::class)->create()とすることで、ファクトリによって生成されたUserモデルがデータベースに保存される
また、createメソッドは保存したモデルのインスタンスを返すので、これが変数$userに代入される

actingAsメソッド

ArticleControllerTest.php
$response = $this->actingAs($user)
            ->get(route('articles.create'));

引数として渡したUserモデルにてログインした状態を作り出す
その上で、get(route('articles.create'))を行うことで、ログイン済みの状態で記事投稿画面へアクセスしたことになり、そのレスポンスは変数$responseに代入される

Arrange-Act-Assertについて

テストの書き方のパターンとして、AAA(Arrange-Act-Assert)というものがある
日本語で言うと、準備・実行・検証

ArticleControllerTest.php
public function testAuthCreate()
    {
        // テストに必要なUserモデルを「準備」
        $user = factory(User::class)->create(); 

        // ログインして記事投稿画面にアクセスすることを「実行」
        $response = $this->actingAs($user)
            ->get(route('articles.create'));

        // レスポンスを「検証」
        $response->assertStatus(200)
            ->assertViewIs('articles.create');
    }

引数と戻り値の型宣言

public function isLikedBy(?User $user): bool

isLikedByメソッドの引数$userの手前には、?Userと記述されている
Userと記述されていることで、引数$userの型はUserクラスのインスタンスであることが宣言されている
さらにUserの手前に?があることで、引数としてnullが渡されることを許容している
PHP7.1から、このnullableな型宣言が使用できる
: boolとあるのは、このメソッドの戻り値が論理型(trueかfalse)であることを宣言している

三項演算子の利用

return $user
    ? (bool)$this->likes->where('id', $user->id)->count()
    : false;

$userがnullでなければ、

(bool)$this->likes->where('id', $user->id)->count()

の結果をメソッドの呼び出し元に返す
$userがnullであれば、falseを返す

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

LaravelでUnitテストだけ実行する

結論、以下のようにしてtestスイートを指定してテストが実行できます。

php vender/bin/phpunit --testsuite Unit

説明

プロジェクトが大きくなってくるとテストも増えてきて、テストを実行するときに部分的に実行したくなると思います。

LaravelだとテストはデフォルトでUnitとFeatureの2つのテストの種類がありますが、このうち高速なUnitテストだけをより高頻度で実行したすることができたら便利ですよね。

Laravelには、デフォルトでphpunit.xmlにテストの設定が書かれていてUnitテストとFeatureテストはそれぞれ別々のtestスイートとして設定されています。

phpunit.xml
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>

これによりphpunitを実行するときに特定のtestスイートを指定するとその部分だけ実行できます。

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

Laravel でいつも最初から仕込むようなファイルたちはパッケージにしてしまおう

はじめに

以前、Laravel の Validation を正しく拡張する という記事を書きましたが、こういったバリデーションの拡張などは最初から仕込みで動いていて欲しいわけです。

なので composer でインストール後、コマンドを叩けばある程度の設定が最初から入ってしまう状態にしたい、という欲求が湧いてきます。

これに答えてくれるのがパッケージ化です。

FuelPHP を使ってた頃は作り方が簡単だったんですが、Laravel のパッケージは定型があまりないので自由度が高い分、ちとやり方がめんどくさいですが整理のためにも記録として残しておきます。

Laravel におけるパッケージとは簡単に言えば vendor ディレクトリ以下にインストールされるファイル群たちの構成要素を自分で一つ増やす感じの配置になりますね。

開発の準備

ディレクトリは任意です。
ただ動作確認的には自分自身の Laravel と組み合わせながら作るのが一番手っ取り早いとは思いますので、開発中は Laravel をインストール後に app たちと同一階層とかにパッケージ専用ディレクトリを作るのが自分としてはわかりやすかったです。

$ php artisan tinker
Psy Shell v0.9.12 (PHP 7.4.3 — cli) by Justin Hileman
>>> base_path();
=> "/FULL/PATH/Parent/PackageDir"

この base_path() と同じ所にいれるのがよかったです。このディレクトリで下記を実行。

$ mkdir packages
$ cd packages
$ composer init

いろいろ聞かれると思いますが、あとでどうせ編集するので適当でよいです。
この composer.json を最終的に下記のように調整しました。一部情報はダミーです。

packages/composer.json
{
    "name": "funaffect/packages",
    "description": "Funaffect Laravel Packages",
    "authors": [
        {
            "name": "moobay9",
            "email": "dummy@mail.dummy"
        }
    ],
    "require": {
        "php": "^7.0",
        "laravel/framework": "5.8.*"
    },
    "autoload": {
        "psr-4": {
            "Funaffect\\LaravelPackages\\": "app/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Funaffect\\LaravelPackages\\Providers\\StartupServiceProvider"
            ]
        }
    }
}

ここでは

  • 依存
  • 名前空間とオートロード
  • Laravel 本体から呼ばれる ServiceProvider

を合わせて設定しています。

名前空間とオートロード

composer.json の autoload 部分ですが、今回は app/ にしていますが、これは任意です。
ただディレクトリは具体的にどこだよ、って感じになりますが、

base_path().'/packages/app/'

です。app と同じ階層に今回は config と resources を置きました。
ディレクトリ構造を Laravel がインストールされているものと合わせたかったからです。

名前空間は任意ですきなものをつかたら良いと思いますが、今後ついてまわるのでちゃんと考えて名前をつけましょう。
今回は psr-4 の下の Funaffect\\LaravelPackages の部分ですね。

サービスプロバイダの準備

今回は Funaffect\\LaravelPackages\\Providers\\StartupServiceProvider という名前にしたサービスプロバイダーでこのパッケージのインストールから利用まで全てをカバーさせます。
設置パスは

base_path().'/packages/app/Providers/StartupServiceProvider.php'

になりますので、ここにファイルを作りました。

サービスプロバイダでできること

最初にファイルを展開することと、アプリケーションが動作中にもサービスプロバイダーを通過して起動するようになります。
このサービスプロバイダで自分がやりたかったのは、下記の数ありました。

  • バリデーションの拡張ファイルの設置
  • バリデーションルールの追加
  • config のファイルを設置
  • 言語ファイルの設置
  • middleware の追加

これは後ほど細部を説明します。

動作させてみる

親のディレクトリ(Laravel の Root)にある composer.json で、一個下のパッケージ開発中の composer.json を認識させる必要があります。

composer.json
"repositories": [
    {
        "type": "path",
        "url": "/packages",
        "symlink": true
    }
],

このあと

composer require funaffect/packages 

でインストールできます。require は packages/composer.jsonname と一致させてください。
場合によってはそのあとで

php artisan vendor:publish --tag=packages

が必要です。

サービスプロバイダに設定していく

サービスプロバイダー内は基本的に register()boot() で設定していきますが、register はドキュメントなどにも記載がありますが、まだロードが終わってなかったりインスタンス化されてないクラスがあったりしますので大抵は boot 側に記載していくことになると思います。

configファイル を app/config へ設置する

packages/app/Providers/StartupServiceProvider.php
    public function boot() {
        $this->publishes([
            __DIR__.'/../../config/packages.php' => config_path('packages.php'),
        ], 'packages');
    }

publishes() で設置するべきディレクトリに設置するべきファイルをおくだけですね。今回はパッケージ内にある packages/config/packages.php をいつも使う config のディレクトリの中へコピーできるようにしました。

ミドルウェアを追加する

ミドルウェアは通常、Route から始まるクラスが出てくるあたりで設定しますので、一般的には routes/web.php の中でやっていることが多いと思います。
ですので、web というミドルウェアグループがありますんで、これに追加してあげる形にしました。

packages/app/Providers/StartupServiceProvider.php
use Funaffect\LaravelPackages\Http\Middleware\ConvertDot;
// (略)

    public function boot() {
        $this->app['router']->pushMiddlewareToGroup('web', ConvertDot::class);
    }

バリデーションを拡張する

バリデーションの拡張はクラスにまとめる場合と一個一個追加する場合があります。
例では大まかに ExtensionValidator でクラスごとバリデーションを拡張、 max_lengthmin_length を個別に追加しています。
それとは別にルールを一つ追加するために publishes で先ほどの config と同じようにファイルを設置しました。
(設置先はパスでわかると思います)

packages/app/Providers/StartupServiceProvider.php
use Funaffect\LaravelPackages\Http\Validators\ExtensionValidator;
    public function boot() {
        // Validation Extension
        $this->app['validator']->resolver(function($translator, $data, $rules, $messages, $attribute) {
            return new ExtensionValidator($translator, $data, $rules, $messages, $attribute);
        });

        $this->app['validator']->replacer('max_length', function ($message, $attribute, $rule, $parameters) {
            return str_replace(':max', $parameters[0], $message);
        });

        $this->app['validator']->replacer('min_length', function ($message, $attribute, $rule, $parameters) {
            return str_replace(':min', $parameters[0], $message);
        });

        $this->publishes([
            __DIR__.'/../Rules/CurrentPassword.php' => app_path('Http/Rules/CurrentPassword.php'),
        ], 'packages');
    }

初期設定を上書きする

config 配下の app.config を毎度毎度書き直していますが、これもめんどくせえとなったときには最初から変えておく手段が欲しいなと思った時に、 mergeConfigFrom() という真逆の動きをする関数がサービスプロバイダ内部にあります。なのでこれの src と dst を入れ替えた mergeConfigFor() という関数を作り、register で実行するようにしました。

packages/app/Providers/StartupServiceProvider.php
    public function register()
    {
        // Config
        $this->mergeConfigFor(__DIR__.'/../../config/app.php', 'app');
    }

    protected function mergeConfigFor($path, $key)
    {
        if (! ($this->app instanceof CachesConfiguration && $this->app->configurationIsCached())) {
            $this->app['config']->set($key, array_merge(
                $this->app['config']->get($key, []), require $path
            ));
        }
    }

ここまでで、大体やりたいことは初期構築時に組み込めるようになりました。

実際に運用する場合

こちらで公開してますので、README にも同じことを書いていますが、Laravel をインストール後、composer.json に以下を追加してください。

composer.json
"repositories": [
    {
        "type": "vcs",
        "url": "https://github.com/moobay9/laravelpackages.git",
        "symlink": true
    }
],
"require": {
    "funaffect/packages": "dev-master"
},

composer install 実行後、php artisan vendor:publish --tag=packages をお忘れなく。

なお、packagist には登録していません。

まとめ

駆け足で振り返ってきましたが、とりあえず備忘録の代わりなのでミスが見つかったら随時修正していきます。
ミスを発見した方はお手数ですがコメントでお知らせください。
git のほうは pull req していただいても構いません。

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

csrfが対策されているのに419エラーが出る

フォームに@csrfはついているので、419エラーが出る意味がわからない。
スクリーンショット 2020-05-22 16.14.21.png

そういえば、少し前に.envに'SESSION_SECURE_COOKIE=true'を入れた気がしたので、
SESSION_SECURE_COOKIE=false
に変更して試したところ419エラーが出ないことがわかった

https://buffturn.net/archives/17889

上記記事によると
SESSION_SECURE_COOKIEは、CookieをSecureにするもので、開発環境がSSL化対応していない場合(HTTPの場合)、Cookieを扱うことができない

以下のようにsecureがついている場合は、Secureになったcookieなので注意(セッションIDは適当)

Set-Cookie: laravel_session=xxxx ... xxxx; expires=Fri, 22-May-2020 11:28:05 GMT; Max-Age=7200; path=/; secure; httponly

入れたものを忘れてしまい結構な時間を取られてしまう愚かなミス

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

Laravelでなぜか二回アクセスしてしまう問題

Laravel 5.8

Flash Sessionに値を持たせてリダイレクトしようとしたが、なぜかFlash Sesionが消えてしまっていた。デバッグすると二回アクセスしている模様(リダイレクトだけでなく、他のページも二回アクセスしていることが判明)

自身でカスタマイズしたMiddlewareが悪さをしていた。

middleware.php
$next($request);

を実施している箇所が一つのミドルウェアで複数ある場合に発生する。
例えば、フレームワークのミドルウェアのhandleメソッドをオーバーライドして親クラスに渡す場合と独自で処理をするなどの分岐をする際は、下記のようにして二回リクエストを飛ばさないようにしてあげましょう。
私の場合はparent::handleにreturnをつけ忘れていた。

middleware.php
     public function handle($request, Closure $next)
      {
          if (/*何かしらの分岐*/) {  
              return parent::handle($request, $next);
          }  
          return $next($request);
      }

参考
https://stackoverflow.com/questions/59377912/laravel-why-every-route-is-executed-twice

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

Laravelで複数のSQL文を実行するときはDB::unpreparedを使う

DBのdumpのような大量のSQLをLaravelから実行するときはDB::unprepared()という関数を使うと、実行することができます。

生のSQL文を実行するときはDB::statement()をよく使いますが、複数のSQL文には対応していません。

以下はファイルに入っている複数のSQL文を読み込んで一括で実行するサンプルコードです。

$sql = file_get_contents(database_path('dump.sql'));
DB::unprepared($sql);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

うんざりするほど詰まった PHP → json → PHP の多次元配列データの受け渡し

CSVをアップロードして、データベースの情報を追記して表示し、DLできるようにする機能を作っていたのですが、追記する情報の中に、シングルクォート(')を含んでいる文字列(例 I'm Fine!)があるとDLできずにとてもとても詰まったので、ここにメモしておきます。

スクリーンショット 2020-05-21 20.31.26.png

環境

PC: MacBook Air
Browser: Chrome
Version: Laravel Framework 5.8.16

参考文献

[PHP]preg_replaceで複数の文字列置換
【PHP】複数文字列の置換(str_replace)
【PHP】JSONがnullになるケース
Javascript|JSONでの、レコードの表現方法(配列、連想配列)→{}と[]の違い
【JavaScript】JSONのparseとstringifyメソッドの使い方
PHP: json_decode を使って Unicode エスケープシーケンスを UTF-8 の文字列に変換する
PHPでjson_encodeとjson_decodeを使ってみる
htmlspecialchars
array_walk_recursiveでクロージャを使う
JSON でのエスケープ処理 (JSONの値に""", "\" を含める場合の処理)
PHP エスケープシーケンスのサンプル
JSONデータに改行コードを入れる方法
JSONについて調べてみた
How to deal with backslashes in json strings php
json_encode() escaping forward slashes
Posting JSON from hidden form field
how to use JSON.stringify and json_decode() properly
JSONのエスケープ
PHPとJavaScriptでHTMLエンティティを扱う時のおさらい
json_encode
json_encode can not escape single quotation marks?
How should I go about adding slashes to only single quotes and ignoring double quotes?
【PHP入門】正規表現で置換する方法
JSON.stringifyは、文字列化されるたびに二重引用符をエスケープします
How do I json_decode string with special chars (“ \ ” )
htmlspecialchars_decode


(:baby_tone2:以下、現象説明から解決まで:baby_tone2:)

現象説明

Laravelで作っている管理画面で、IDが載っているCSVを管理画面上でアップロードしたら、ajaxで、PHP側でそのIDをもとに紐づく情報をデータベースから取ってきて、CSVのデータにくっつけて、テーブルに表示し、CSVとしてDLもできるようにする機能を作ったのですが、
紐づく情報の中に、「'」(シングルクオート)が入っている場合に、JavaScriptのエラーが出てDLできない事象が起こりました。そして、Javascript側のエラーを解消すると、今度はPHP側でエラーが起こってしまう、という事態になりました。

まず、Javascript側でエラーになってしまっていた原因は、
そもそもの仕組みとして、DLボタンが押されたら、

scriptでformを生成してhiddenのvalue値にjson形式でデータを入れてsubmitさせる

という形だったのですが、
そのvalue値が、「'」(シングルクオート)があることにより、途中で途切れてしまう為、第一弾のエラーが起こってしまっておりました。

エラーが起きたコード
$(document).ready(function(){
    //CSVダウンロード
    function download_csv(){

    // csvデータを作成
    var csvData = '<?= json_encode($csv_data) ?>';

    // ダウンロードを実行
    $('<form target="_blank" action="【PHP側のURL】" method="POST">' +
        '<?= csrf_field() ?>' +
        '<input type="hidden" name="csv_data" value=\'' + JSON.stringify(csvData) + '\'>' +
        '</form>').appendTo(document.body).submit();
    }
    $("#dl-btn").on('click', function(e) {
        download_csv();
    });
});

そこで、json_encodeを見直し、「'」(シングルクオート)をエスケープしたりすることで、javascriptのエラーを起こさずに、PHP側に遷移できるようになったのですが、

しかしながら、今度はエラー第二弾。
PHP側で、POSTで送られてきた上記CSVのデータをjson_decode()した後に、foreachを回していたのですが、
そのforeachの部分で、エラーが起きてしまいました。

デバッグしてみると、json_decode()した後のデータが入っているはずの$csv_datがNULLとなっており、
NULLなのにforeachを回そうとしてエラーになっておりました。

エラーが起きたコード
    public function dlCsvConversion(Request $request)
    {
        assert(isset($_POST['csv_array']));
        $csv_dat = json_decode(json_decode($_POST['csv_array'], true),true);
        $csv = '';
        foreach ($csv_dat as $value){
            foreach ($value as $k => $val){
                if ($k === count($value)-1) {
                    $csv.=$val;
                } else {
                    $csv.=$val."\t";
                }
            }
            $csv.="\r\n";
        }
        header('Content-Encoding: UTF-16LE');
        header('Content-type: text/csv; charset=UTF-16LE');
        header('Content-Disposition: attachment; filename="convertedCsv-'.date('Ymd').'.csv"');
        header('Pragma: no-cache');
        header('Expires: 0');

        echo chr(255) . chr(254) . iconv("UTF-8", "UTF-16LE", $csv);
        exit;
    }

だけど、POST値はきちんと送られてきております。
なのに、json_decode()の結果がNULL。

検索してみると、json_decode()に失敗するとNULLとなってしまうことがわかりました。
そこで、色々と改善策を検索して試してみたのですが、
今回、json配列が[](鍵カッコ)で括られた、行列のみ多次元配列のデータだった為、key:valueのような形の{}で囲われたjson配列の記事はたくさんあったのですが、その違いのためかうまくいかない手法ばかりでした。

解決

ただ、試行錯誤しているうちに、

POST値がjson_encode()したはずなのに、なぜか「'」(シングルクオート)などがdecode済みの形で入ってきてしまっていたのを

javascript側を

json_encode(json_encode($csv_data, JSON_HEX_APOS))

上記のように最初に「'」(シングルクオート)だけ指定してjson_encodeし、さらに全体をjson_encodeし、JSON.stringify()は使わないようにすることで、

PHP側にencodeしたままの形で渡せるようになり、
さらに、今までは

""[[],[]]""

上記のように、javascript側で2回json_encode()していることで、「"」(ダブルクオーテーション)が入れ子になってしまっているのをjson_encode()を入れ子にして2回実施することで、解凍していたのですが、
それをpreg_replace()で最初と最後の「"」(ダブルクオーテーション)を取り除き、json_encode()を1回だけにしたところ、json_decode()がSyntax errorでNULLになってしまうのを無事解消することができました!!

修正後のコード(javascript側)
$(document).ready(function(){
    //CSVダウンロード
    function download_csv(){

    // csvデータを作成
    var csvData = '<?= json_encode(json_encode($csv_array, JSON_HEX_APOS)) ?>';

    // ダウンロードを実行
    $('<form target="_blank" action="【PHP側のURL】" method="POST">' +
        '<?= csrf_field() ?>' +
        '<input type="hidden" name="csv_array" value=\'' + csvData + '\'>' +
        '</form>').appendTo(document.body).submit();
    }
    $("#dl-btn").on('click', function(e) {
        download_csv();
    });
});
修正後のコード(PHP側)
    public function dlCsvConversion(Request $request)
    {
        assert(isset($_POST['csv_array']));
        $search = array('/^"/','/"$/');
        $replace = array('','');
        $csv_data = json_decode(preg_replace($search, $replace, $_POST['csv_data']),true);
        $csv = '';
        foreach ($csv_data as $value){
            foreach ($value as $k => $val){
                if ($k === count($value)-1) {
                    $csv.=$val;
                } else {
                    $csv.=$val."\t";
                }
            }
            $csv.="\r\n";
        }
        header('Content-Encoding: UTF-16LE');
        header('Content-type: text/csv; charset=UTF-16LE');
        header('Content-Disposition: attachment; filename="paygentCsvConverted-'.date('Ymd').'.csv"');
        header('Pragma: no-cache');
        header('Expires: 0');

        echo chr(255) . chr(254) . iconv("UTF-8", "UTF-16LE", $csv);
        exit;
    }

:alien:振り返り:alien:

そもそものコードもちょっとおかしかったのですが、どん詰まり状態から開放されてとても嬉しい気分です。
直したコード的にはあっけなかったのですが、エンコードのデバッグはとてもわかりづらくて辛かった。。
Unicode文字列とかまで試したりして、結局は使いませんでしたが、勉強にはなりました!
POST値がencode済だったのは多分JSON.stringify()していたせいですかね、、
直せたけど、曖昧なままな部分もある。。でもとりあえず、よしとしておきます!

以上!

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

【Laravel】キャッシュをクリアするコマンド一覧

はじめに

開発をしている中でコードの変更が反映されないことがあります。
初学者なら、なんで変更が反映されないの??ってなりますし、経験を積んだ今でもキャッシュクリアのコマンドを調べてるので一覧にまとめたいと思います。

キャッシュクリアするコマンド一覧

キャッシュクリア

コード変更したのに反映されない場合は、とりあえずこのコマンドを実行しろ的な基本コマンド

$ php artisan cache:clear

ルートキャッシュ

web.phpファイルなどでルートを変更したのに反映されないって時はルートキャッシュを使えば上手くいくはずです。

$ php artisan route:cache

configキャッシュクリア

envファイルを変更したらconfigキャッシュをクリアしてあげないと反映されないことが多いです。

$ php artisan config:cache

viewキャッシュクリア

bladeファイルやvueファイルを編集したのに反映されない時はviewキャッシュをクリアします。

$ php artisan view:cache

Seederファイルを作った時

Seederでダミーデータを作成した場合、php artisan db:seedコマンドを実行する前に下記を実行します。

$ composer dump-autoload

開発中にわりと実行するコマンドはだいたいこんなもんかなって思います。

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

Laravel 7 バッチ処理でusersテーブルのメールアドレスにメールを送る

目的

  • Authで実装した認証機能で使用するusersテーブルに登録されているメールアドレスに順々にメールを送信するバッチの作成方法をまとめる

実施環境

  • ハードウェア環境(下記の二つの環境で確認)
項目 情報
OS macOS Catalina(10.15.3)
ハードウェア MacBook Pro (16-inch ,2019)
プロセッサ 2.6 GHz 6コアIntel Core i7
メモリ 16 GB 2667 MHz DDR4
グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いて導入
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いて導入

前提情報

概要

  1. バッチファイルの作成
  2. 確認

概要

  1. バッチファイルの作成

    1. アプリ名ディレクトリで下記コマンドを実行してバッチファイル(コマンドファイル)を作成する。ファイル名を「SendMailCommand.php」とする。

      $ php artisan make:command SendMailCommand
      
    2. アプリ名ディレクトリで下記コマンドを実行して先に作成したコマンドファイルを開く。

      $ vi app/Console/Commands/SendMailCommand.php
      
    3. 開いたコマンドファイルを下記の様に修正する。(開いたファイルで作成するコマンドでメールの送信処理を行う)

      アプリ名ディレクトリ/app/Console/Commands/SendMailCommand.php
      <?php
      
      namespace App\Console\Commands;
      
      use Illuminate\Console\Command;
      //下記を追加する
      //usersテーブル用のモデルファイルを紐づける
      use App\User;
      //メール送信用ファサードを紐づける
      use Illuminate\Support\Facades\Mail;
      //上記までを追加する
      
      class SendMailCommand extends Command
      {
          /**
           * The name and signature of the console command.
           *
           * @var string
           */
          //下記を修正する
          protected $signature = 'app:send_mail_users';
      
          /**
           * The console command description.
           *
           * @var string
           */
          //下記を修正する
          protected $description = 'usersテーブルのemail全てにメールを送信する';
      
          /**
           * Create a new command instance.
           *
           * @return void
           */
          public function __construct()
          {
              parent::__construct();
          }
      
          /**
           * Execute the console command.
           *
           * @return mixed
           */
          public function handle()
          {
              //下記を追加・修正する
              $users_infos = User::all();
      
              foreach ($users_infos as $users_info) {
                  echo $users_info['email']."\n";
      
                  Mail::raw("これはテストメールです", function($message) use ($users_info)
                  {
                      $message->to($users_info->email)->subject('test');
                  });
              }
              //上記までを追加・修正する
          }
      }
      
  2. 確認

    1. アプリ名ディレクトリで下記コマンドを実行してコマンドが追加されている事を確認する。

      $ php artisan list
      
    2. 先のコマンドの出力の中にapp:send_mail_usersがある事を確認する。

    3. アプリ名ディレクトリで下記コマンドを実行してバッチ処理が正常に動作するか確認する。(実際にメールが送信されるため注意する!!!)

      $ php artisan app:send_mail_users
      
    4. usersテーブルに登録されているメールアドレスの受信ボックスを確認し、下記の様なメールが届いていれば実装完了である。

      test_-_shun_okawa_gmail_com_-_Gmail.png

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

LaravelでS3を操作する時はアクセスキー&シークレットキーではなくIAMロールを使うこと

LaravelからS3を使用する際はStorageファサードでS3を操作することが多いと思う。
その際に私がググった限りでは、ほとんどがアクセスキーとシークレットキーをソースコードにべた書きしているケースばかりだった。
しかし、こちらはアンチパターンなので、ec2で上記を行う場合はIAMロールを使用して、アクセスキー等はAWS側に隠ぺいしてもおう。

ec2にIAMロールを割り当てる手順は転がっているので、ここでは割愛。

Laravel側

config/filesystems.php

    's3' => [
        'driver' => 's3',
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION'),
        'bucket' => env('AWS_BUCKET'),
        'url' => env('AWS_URL'),
        'endpoint' => env('AWS_ENDPOINT'),
    ],

.env

    AWS_ACCESS_KEY_ID=
    AWS_SECRET_ACCESS_KEY=
    AWS_DEFAULT_REGION=ap-northeast-1
    AWS_BUCKET=<<バケット名>>

こう記載すると403エラーでS3にアクセスできなくなるので、必ず上記のように空にすること
.env

    AWS_ACCESS_KEY_ID=null
    AWS_SECRET_ACCESS_KEY=null

開発環境はec2じゃなくてローカルだよ~という人

その場合はキーを記載しましょう

.env

    AWS_ACCESS_KEY_ID=<<アクセスキー>>
    AWS_SECRET_ACCESS_KEY=<<シークレットキー>>
    AWS_DEFAULT_REGION=ap-northeast-1
    AWS_BUCKET=<<バケット名>>

ちなみに①

IAMロール割り当てはインスタンス内部でアクセスキー等をちゃんと持っている。
Laravelはfilesystems.phpのkeyとsecretが空だったときは、こちらを読み込むようになっているので、接続できるということですね。

aws configure list

          Name                    Value             Type    Location
          ----                    -----             ----    --------
       profile                <not set>             None    None
    access_key     ****************XXXX         iam-role    
    secret_key     ****************XXXX         iam-role    

ちなみに②

S3はaws-sdk-phpを使用かつ上記をすることでIAMロール割り当てで接続できるが、
他のAWSリソースの中には、aws-sdk-phpではダメでaws-sdk-php-laravelを使用しないと接続できないものがあるらしい。

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