- 投稿日:2020-01-24T23:33:49+09:00
外為で勝つために その5 ~ Laravel 編
前回「外為で勝つために その4 ~ LINE 通知編」の続きです。
ご無沙汰してます。
ポータルサイトを作るための材料を用意したり、NISA で株の買い付けしたりしてました。
今年もどうぞよろしくお願いします。ポータルサイトを作るにあたり、
チャートなどを表示するために、データベースに接続する必要があります。データベース接続といえば PHP ですが
ここはオシャレに Laravel フレームワークを使いたいと思います。
PHP だけでも構築できますが、勉強も兼ねてということで。PHP バージョン確認
早速問題になるのが PHP のバージョンです。
今回導入する Laravel は 6.7.0 で、土台として PHP 7.2 以上が必要なのだそう。Raspberry Pi の標準的なインストール方法では PHP 7.0.33 までしか入りません。
$ php -v PHP 7.0.33-0+deb9u6 (cli) (built: Oct 24 2019 18:50:20) ( NTS ) Copyright (c) 1997-2017 The PHP Group Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies with Zend OPcache v7.0.33-0+deb9u6, Copyright (c) 1999-2017, by Zend TechnologiesPHP 7.3 をインストール
ということで、裏技的に PHP 7.3 をインストールします。(執筆時点で安定した最新版)
まずは管理者ログインして、apt に必要なキー情報を登録します。$ sudo -i # wget -O - https://packages.sury.org/php/apt.gpg | apt-key add - stdout へ出力完了 [1769/1769]パッケージリストを更新して PHP 7.3 を探せるようにします。
# echo "deb https://packages.sury.org/php/ stretch main" >> /etc/apt/sources.list.d/php.list # apt-get update パッケージリストを読み込んでいます... 完了インストールできる PHP のバージョンを確認します。
7.3 の文字が見えますね。# apt-cache policy php php: インストールされているバージョン: 1:7.0+49 候補: 2:7.3+70+0~20191118.18+debian9~1.gbp66b4ed バージョンテーブル: 2:7.3+70+0~20191118.18+debian9~1.gbp66b4ed 500 500 https://packages.sury.org/php stretch/main armhf Packages *** 1:7.0+49 500 500 http://raspbian.raspberrypi.org/raspbian stretch/main armhf Packages 100 /var/lib/dpkg/statusPHP 7.3 をインストールします。バージョンは必ず指定してあげましょう。
インストールはあっけなく終わるので、管理者からログアウトしてバージョンを確認します。# apt-get install php7.3 # exit ログアウト $ php -v PHP 7.3.12-1+0~20191128.49+debian9~1.gbp24559b (cli) (built: Nov 28 2019 07:38:18) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.12, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.3.12-1+0~20191128.49+debian9~1.gbp24559b, Copyright (c) 1999-2018, by Zend TechnologiesPHP 7.3.12 が無事インストールされました。
Laravel インストールの下準備
Laravel にはいくつか必要なモジュールがあるそうで、
下記のコマンドを実行して表示されないモジュールをインストールするようなことが、どこかに書いてありました。(どこだったかはもう完全に忘れました)$ php -m | grep -e openssl -e PDO -e mbstring -e tokenizer -e ^xml$ -e ctype -e json -e bcmath ctype json openssl PDO tokenizer
mbstring
とxml
とbcmath
がないですね。
モジュール名それぞれの先頭に php- を付けてインストールしておきます。インストールが終わったら、同じ確認コマンドを実行して再確認します。問題なさそうですね。
$ sudo apt-get install php-bcmath php-mbstring php-xml $ php -m | grep -e openssl -e PDO -e mbstring -e tokenizer -e ^xml$ -e ctype -e json -e bcmath bcmath ctype json mbstring openssl PDO tokenizer xmlComposer のインストール
Laravel は PHP のフレームワークでありパッケージの1つです。
PHP 関係のパッケージは Composer で依存性を一元管理するらしいです(多分)。いわゆるパッケージマネージャーですね。これを先にインストールしておきます。それにしても、やたら味のある Web サイトのトップだったのでスクショしておきました。
良いセンスを感じますねぇ。それはさておき、
インストーラを HTTP GET して、そのままインストールします。$ curl -sS https://getcomposer.org/installer | php All settings correct for using Composer Downloading... Composer (version 1.9.1) successfully installed to: /home/pi/composer.phar Use it: php composer.phar $ sudo mv composer.phar /usr/local/bin/composer $ composer -v ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / \____/\____/_/ /_/ /_/ .___/\____/____/\___/_/ /_/ Composer version 1.9.1 2019-11-01 17:20:17インストールはあっさり完了しました。
Laravel のインストール
いよいよ本命の Laravel をインストールします。
Composer 経由でインストールコマンドを実行します。$ cd /home/pi/.config/composer $ composer global require laravel/installer Problem 1 - laravel/installer v3.0.1 requires ext-zip * -> the requested PHP extension zip is missing from your system. - laravel/installer v3.0.0 requires ext-zip * -> the requested PHP extension zip is missing from your system. - Installation request for laravel/installer ^3.0 -> satisfiable by laravel/installer[v3.0.0, v3.0.1]. Installation failed, deleting ./composer.json.なんか失敗しましたね。
zip が足らんのでインストールが続行できないとのこと。ならば php-zip をインストールしてリトライ。
$ sudo apt-get install php-zip $ composer global require laravel/installer Changed current directory to /home/pi/.config/composer Using version ^3.0 for laravel/installer ./composer.json has been created Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 14 installs, 0 updates, 0 removals - Installing symfony/process (v5.0.1): Downloading (100%) - Installing symfony/polyfill-ctype (v1.13.1): Downloading (100%) - Installing symfony/filesystem (v5.0.1): Downloading (100%) - Installing psr/container (1.0.0): Downloading (100%) - Installing symfony/service-contracts (v2.0.1): Downloading (100%) - Installing symfony/polyfill-php73 (v1.13.1): Downloading (100%) - Installing symfony/polyfill-mbstring (v1.13.1): Downloading (100%) - Installing symfony/console (v5.0.1): Downloading (100%) - Installing ralouphie/getallheaders (3.0.3): Downloading (100%) - Installing psr/http-message (1.0.1): Downloading (100%) - Installing guzzlehttp/psr7 (1.6.1): Downloading (100%) - Installing guzzlehttp/promises (v1.3.1): Downloading (100%) - Installing guzzlehttp/guzzle (6.5.0): Downloading (100%) - Installing laravel/installer (v3.0.1): Downloading (100%) symfony/service-contracts suggests installing symfony/service-implementation symfony/console suggests installing symfony/event-dispatcher symfony/console suggests installing symfony/lock symfony/console suggests installing psr/log (For using the console logger) guzzlehttp/psr7 suggests installing zendframework/zend-httphandlerrunner (Emit PSR-7 responses) guzzlehttp/guzzle suggests installing psr/log (Required for using the Log middleware) guzzlehttp/guzzle suggests installing ext-intl (Required for Internationalized Domain Name (IDN) support) Writing lock file Generating autoload filesインストールできた感じは微塵もありませんが、無事完了したようです。
Laravel Web Artisan の作成
もう少しです。
インストールは完了したので、Laravel 実行環境である(プロジェクトとも呼ばれる)Web Artisan(ウェブ職人)を作成します。事前に composer/vendor/bin にパスを通しておきます。
作成先フォルダは /var/www/html/MarketMonitor にしました。
laravel new <Project Name>
で作成します。今回はひねりなく laravel にしました。$ export PATH="~/.config/composer/vendor/bin:$PATH" $ cd /var/www/html/MarketMonitor $ laravel new laravel Package manifest generated successfully. Application ready! Build something amazing.「アプリケーションの準備ができました!素晴らしいものを作りましょう」
と言われたらインストール完了です。もちろん作りますよ。素晴らしいものを。Laravel のテスト
「すばらっ」なアプリケーションを作成する前に、実際動くかどうかをテストしましょう。
書き込みが発生する(であろう)ディレクトリ配下のファイルに、パーミッションを与えておきます。
php artisan serve
を実行するとサーバーが起動します。$ chmod -R 777 /var/www/html/MarketMonitor/laravel/storage $ chmod -R 777 /var/www/html/MarketMonitor/laravel/bootstrap/cache $ cd /var/www/html/MarketMonitor/laravel $ php artisan --version Laravel Framework 6.7.0 $ php artisan serve Laravel development server started: http://127.0.0.1:8000 [Wed Dec 11 14:06:27 2019] 127.0.0.1:47120 [200]: /favicon.icottp://127.0.0.1:8000 にアクセスしろ、とのことです。
成功ですね。
これでデータベースにアクセスし放題です!
- 投稿日:2020-01-24T20:04:05+09:00
LaravelからSalesforceへのデータ連携入門
これはなに
LaravelからSalesforceにデータを同期する方法について紹介します。
別システムで管理してるデータをSalesforceに同期させることで、
営業やカスタマーサクセスなど他部署でのデータ活用が可能になり、業務効率化が期待できます。そういうわけでやってくれと依頼されたのでやってみました。
Salesforce何もわからない状況から始まってようやくデータ同期の道筋ができた状況です。Salesforceにデータを同期する方法
別システムからSalesforceにデータを同期する方法にはいくつかありますが、
今回はおそらく一番一般的であろうREST APIを使います。Lightning Platform REST API
ドキュメントはこちら https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/intro_what_is_rest_api.htm
Lightning Platformってなに? って感じですが、
最近のSalesforceプラットフォームのことで、昔のはSalesforce Classicというようです。
あまり気にせず行きましょう。APIいろいろあるんですが、正直あまり見てません。
次に紹介するラッパーが便利だったからです。LaravelからSalesfoceにデータ同期するのに便利なパッケージ
LaravelからSalesfoce REST APIを便利に使うパッケージを探したところ
omniphx/forrest
というものを見つかりました。
Salesforce REST API Client for Laravel
https://github.com/omniphx/forrest
Forrestの使い方
Installation
Laravel6前提です
composer.json
に"omniphx/forrest": "2.*"を追加して
composer updateインストールが完了すると
config/app.php
に以下のコードが追加されるようです。
私は追加されてなかったので自分で追加しました。
それぞれproviders
とaliases
に追加します。config/app.phpOmniphx\Forrest\Providers\Laravel\ForrestServiceProvider::class 'Forrest' => Omniphx\Forrest\Providers\Laravel\Facades\Forrest::classこれでLaravelの他のFacade(LogやCache)と同じように、コードのどこでも
Forrest
と書くとFacadeが実行できるようになりました。設定ファイルの外出しと環境変数
このままではforrestの設定ファイルがvender配下にあって変更できないので、
以下のコマンドでconfig/
配下に設定ファイルを移動させます。php artisan vendor:publish他にも設定ファイルの外出しができる場合はどれを外出しするか聞かれますが、configってやつを選べばよかったと記憶してます。
次に
.env
に環境変数を追加します。.envCONSUMER_KEY=123455 CONSUMER_SECRET=ABCDEF CALLBACK_URI=https://test.app/callback LOGIN_URL=https://login.salesforce.com USERNAME=mattjmitchener@gmail.com PASSWORD=password123どんな値を設定するかはあとで説明します。
私の場合は、
PASSWORD
みたいな変数名だと他の変数と被りそうだったので
config/forrest.phpをいじってSALESFORCE_PASSWORD
のように名前を変えました。認証方法の設定
Salesforceに接続する方法はOAuthとパスワード認証の2種類ありますが、
今回はパスワード認証を使用します。config/forrest.php/* * Options include WebServer, UserPassword, and UserPasswordSoap */ - 'authentication' => 'WebServer', + 'authentication' => 'UserPassword',アクセストークンの保存先をキャッシュにしておきます。
config/forrest.php/* * Where do you want to store access tokens fetched from Salesforce */ 'storage' => [ - 'type' => 'session', // 'session' or 'cache' are the two options + 'type' => 'cache', // 'session' or 'cache' are the two optionsSalesforceに接続アプリケーションを追加する
今回はパスワード認証を使う前提で進めます。
1. Salsesforceプラットフォームにログインする
2. 設定 > アプリケーションマネージャ(左のメニュー) からアプリケーションマネージャの画面を表示する
3.新規接続アプリケーション
をクリック
4. 必須項目を埋める接続アプリケーションができると、CONSUMER_KEYなどが発行されるのでこれをコピーして.envに貼り付けます。
.envCONSUMER_KEY=123455 CONSUMER_SECRET=ABCDEF # CALLBACK_URI=https://test.app/callback パスワード認証の場合これはいらない LOGIN_URL=https://test.salesforce.com # 本番環境の場合はhttps://login.salesforce.com USERNAME=mattjmitchener@gmail.com # Salesforceのログインアカウントと同じ PASSWORD=password123使い方
認証
認証はこれだけです。
Forrest::authenticate();データ取得
データの取得にはSQLライクなSOQLというのを使います。
Forrest::query('SELECT Id FROM Account');{ "totalSize": 2, "done": true, "records": [ { "attributes": { "type": "Account", "url": "\/services\/data\/v30.0\/sobjects\/Account\/001i000000xxx" }, "Id": "001i000000xxx" }, { "attributes": { "type": "Account", "url": "\/services\/data\/v30.0\/sobjects\/Account\/001i000000xxx" }, "Id": "001i000000xxx" } ] }1度に2000件しか取れないという制限がありますが、
次のデータ
を取得する方法も簡単です。詳しくはREADMEを見てください。データ同期(Upsert)
データの同期は以下の
patch
メソッドを使います。$externalId = 'XYZ1234'; Forrest::sobjects('Account/External_Id__c/' + $externalId, [ 'method' => 'patch', 'body' => [ 'Name' => 'Dunder Mifflin', 'Phone' => '555-555-5555' ] ]);Upsert(Salesfoce上にすでにデータがあれば上書き、なければ新規作成) をこれだけで実現できます。
そのためにはSalesforce上のオブジェクトに、
外部ID
属性のカラムを追加する必要があります。外部IDとpatchを使った同期の例
Salesforceのオブジェクトには、外部IDという属性のカラムを追加することができます。
外部システムからは外部キーを使って簡単にオブジェクトを特定し、書き込むことができます。
外部IDには、外部システム上のオブジェクトのプライマリーキーを使うと、
外部システム上のレコードとSalesforce上のレコードが一対一の関係になって都合がいいと思います。前提
Salesforceに同期したいLarave上のオブジェクトをUserというEloquentモデルだとします。
Userモデルはidというプライマリーキーを持つものとします。Salesforce側の準備
設定 > オブジェクトマネージャ > 新規
からUserオブジェクトを作ります設定 > オブジェクトマネージャ >User > 項目とリレーション > 新規
から数値型のuser_id
という項目を作ります。- 作成時のオプションで
ユニーク
外部ID
というフラグをONにします。- これでオブジェクトの型ができました。
Laravel側からUpsertする
$user_id = $user->id; Forrest::sobjects('User__c/user_id__c/' . $user_id, [ 'method' => 'patch', 'body' => [ 'Name' => 'This is new user!', ] ]);
User__c
は、UserオブジェクトのAPI参照名です。
user_id__c
は、user_idカラムの項目名です。
$user_id
はあなたが書き込みたいUserオブジェクトのIDです。
Salesforce上に$user->id
と一致するuser_idを持つUserオブジェクトが存在しなければ新規追加し、存在すれば上書きをしてくれます。patchメソッドを使うことで、データの同期を簡単に行うことができました。
参照の同期について
Salesforceには、オブジェクトの参照関係があり、「どのオブジェクトを参照するか」という情報の同期が必要なケースがあります。
どのオブジェクトを参照するかの指定も外部IDを使えば簡単にできます。
詳しくは述べませんが、以下の資料が参考になるはずです。
https://developer.salesforce.com/docs/atlas.ja-jp.api_rest.meta/api_rest/dome_upsert.htmまとめ
- LaravelからSalesforceへのデータ同期は
Forrest
が便利です- Salesforceの
外部ID
という仕組みを使えばデータ同期(Upsert)が簡単です以上、参考になりましたら幸いです。
- 投稿日:2020-01-24T16:22:06+09:00
artisanコマンドの引数にハイフン始まりの値を渡したい
引数にハッシュ値を指定するartisanコマンドを作っていたのですが、
ハッシュ値がハイフン始まりのときにオプション扱いされてエラーになりました。。現象
例)ハッシュ値が
-LA12_ed
php artisan hoge:fuga -LA12_edエラーになる。
The "-L" option does not exist.
対策
そんなときはエスケープすればいいよね。
エスケープ文字は--
でした。ハイフンハイフン半角スペース!php artisan hoge:fuga -- -LA12_edこれで無事引数を受け取れます。
参考
https://stackoverflow.com/questions/35493936/laravel-custom-command-argument-with-dash
- 投稿日:2020-01-24T15:54:18+09:00
Laravelでトランザクションをネストせずに新しいトランザクションを切る
毎度同じことをやろうと思った時にどうやればいいか忘れてしまうので備忘も兼ねて
はじめに
Laravelでは以下のコードのようにするとトランザクションをネストしてSAVEPOINTを作成するため、やんごとなき事情で新しいトランザクションを切りたいなーと思ってもうまくいきません。
(ID発番が連番ではない場合に別テーブルでID発番を管理しトランザクションを分けることでロックする時間を短くするとか)DB::beginTransaction(); { DB::beginTransaction(); Animal::create(['name' => 'ぺんぎんさん']); DB::commit(); } DB::rollBack(); // ぺんぎんさんの挿入もロールバックされる!SAVEPOINTってなんだ!って方は以下の記事が非常にわかりやすかったです。
●トランザクションのネストの使い方まとめた(初心者向け)
https://qiita.com/_natsu_no_yuki_/items/e1db2a132cbff740896d実装方法
トランザクションを乱立させることはそう多くないと思うので、簡単にできる別コネクションを作成する方法を記載します。
準備
config/database.php
にデフォルトで利用している接続情報をまるっとコピーします。database.php// 既存の接続情報 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], // ★mysqlの項目を丸コピした接続情報 'mysql_2' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ],実装
新しいトランザクションを切りたい場合は、DBファサードの
connection
で追加した接続情報のキーを設定します。
Eloquentを利用する場合は、その中でModelのインスタンスを作成し、setConnection
で追加した接続情報のキーを設定します。
- 手動トランザクションの場合
// デフォルトのコネクションでトランザクションの開始 DB::beginTransaction(); { // 追加した接続情報をセットして、トランザクションの開始 $mysql2 = \DB::connection('mysql_2'); $mysql2->beginTransaction(); // Modelのインスタンスを作成し、追加した接続情報をセットする $animal = new Animal(); $animal->setConnection('mysql_2'); $animal->create(['name' => 'ぺんぎんさん']); // mysql_2のコネクションをコミットする $mysql2->commit(); } // デフォルトのコネクションをロールバックする DB::rollBack(); // ぺんぎんさんのデータもコミットされている!
- トランザクションメソッドの場合
DB::transaction(function () { DB::connection('mysql_2')->transaction(function () { $animal = new Animal(); $animal->setConnection('mysql_2'); $animal->create(['name' => 'ぺんぎんさん']); }); throw new Exception(); // ぺんぎんさんのデータもコミットされている! });おわりに
なんだかんだネストではなく、別トランザクションを切りたくなることはあるので、
newTransaction
とかできると嬉しいんだろうなというお気持ちでした。
- 投稿日:2020-01-24T15:43:22+09:00
お問い合わせ管理システムの設計 Laravel + Docker
はじめに
こちらの記事は課題を取り組むために書いています。どのように自分で課題のアプリを設計するかを大まかに示します。
Laravelは初めて使うことになるので、ここ二日間Dockerで環境を構築しLaravelを勉強していました。ですので、今からどのようにアプリを設計するかを書いていきたいと思います。
技術選定
課題では、Laravel / Docker / MySQL という組み合わせでシステムを構築するということでした。しかしながら、MySQLのコンテナーのステータスがrestartingの状態から変わらないというエラーが発生し直せませんでした。
ですので、Laravel / Docker / Postgres / Sass-bootstrapで始めたいと思います。
MySQLを諦める理由
エラーログを確認したところ、
mysql initializing databsase error -- initialize specified but the data directory has files in it. aborting
と書いてありました。ですので、そのディレクトリーに何らかの形で発生したファイルを削除したいのですがコンテナーもUPの状態ではないのでdocker-compose.ymlからコマンドを指定して空の状態にしようと試してみました。
volumes: - "./mysql:/var/lib/mysql" command: --innodb-use-native-aio=0Github上の参考フォーラム: https://github.com/docker-library/mysql/issues/69
ですが状況は変わらず、同じエラーログが出ておりMySQLの使用を断念することにしました。
半日くらい解決策を調べていましたが、見つかりませんでした。フォーラムでは同じエラーを経験し直せない方たちはmariaDBなどを代わりに使うということでした。自分も他のDBを使おうと思い、Postgresのコンテナーをセットアップしてみました。
Postgresの方はコンテナもUPの状態になり、使えそうなのでまず開発ではそちらを使うことにしました。
Sass-Bootstrap
Laravelと言えばVueをフロントで使いたいところですが、LaravelとVueを一辺に勉強ができるかまだ分からないのでBootstrapで簡潔にページを作っていくことにしました。
実はBootstrap-Material-Designでグーグルっぽくしたいと思ったのですが、Laravelでは一部のスタイリングに不具合が生じることが分かったので通常のBootstrapを使い、余裕があればMaterial Designっぽくアレンジしていけたらと思っています。
アプリの設計
お問い合わせ管理システムは、クライアント側のフォーム画面とAdmin側の管理画面の大きく二つのページに分けます。
すなわちデータベースは最低、クレーム用件と管理者の情報をまとめるものが必須となります。
その二つができれば、管理者システム内でのクレーム用件へのコメント機能を追加します。その際には、コメントの情報をまとめるテーブルが必要となります。
クレーム要件をまとめるdata schema:
create_complaints_table.phppublic function up() { Schema::create('complaints', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedInteger('admin_id'); $table->string('firstname'); $table->string('lastname'); $table->string('email'); $table->string('phone'); $table->string('product'); $table->string('title'); $table->text('content'); $table->string('status')->default('not addressed'); $table->timestamps(); }); }フォームから (姓 / 名 / メールアドレス / 電話番号 / 不良製品番号 / タイトル / 内容 ) を取得します。
(管理者ID / ステータス)については、管理者側の行動により左右されます。
クレーム用件が特定の管理者に渡れば、管理者IDが付与されます。
またステータスは、提出されてまだ管理者に渡っていない状態が'not addressed'
特定の管理者に渡ることによって'in progress'
そして管理者からの返信が済んだら'completed'に変わります。
管理者の情報をまとめるdata schema:
create_admins_table.phppublic function up() { Schema::create('admins', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->integer('batches')->default('0'); $table->string('status')->default('available'); $table->timestamps(); }); }管理者たちの (姓 / 名 / メールアドレス / パスワード ) を予め設定します。
Batchesには一日に対応したクレームの数を保存します。
Statusにはその管理者が働ける状態かを示します。(available / unavailable)
クレーム用件と管理者情報の関係
二つのテーブルの関係は、one to one relaitionshipとなります。一人の管理者が一つのクレームを管理します。お問い合わせ管理システムでは、誰かが対応中のクレームには、他の管理者が対応できないようにします。ですので対応中のクレームは一人の管理者によって所有されます。
Admin.phpclass Admin extends Model { protected $guarded = []; public function complaint() { return $this->hasOne(Complaint::class); } }Complaint.phpclass Complaint extends Model { protected $guarded = []; public function admin() { return $this->belongsTo(Admin::class); } }管理者のコメント機能
こちらは、管理者が対応中の状態にしたクレームに対してコメントができるという機能です。クレームに関わった管理者のコメントが表示されます。(初めに対応した管理者がコメントをしクレームを未対応に戻した場合、新しく対応している管理者がコメントもできる)
コメントをまとめるdata schema:
create_comments_table.phppublic function up() { Schema::create('comments', function (Blueprint $table) { $table->increments('id'); $table->text('body'); $table->integer('admin_id')->references('id')->on('admins')->onDelete('cascade'); $table->integer('complaint_id')->references('id')->on('complaints')->onDelete('cascade'); $table->timestamps(); }); }クレーム用件・管理者とコメントテーブルの関係
コメントテーブルとの関係はそれぞれ、one to many relationshipとなります。
Comment.phpclass Comment extends Model { protected $fillable = ['body', 'admin_id', 'complaint_id']; public function claim() { return $this->belongsTo('Complaint::class'); } public function admin() { return $this->belongsTo('Admin::class'); } }Complaint.phpclass Complaint extends Model { protected $guarded = []; public function admin() { return $this->belongsTo(Admin::class); } public function comments() { return $this->hasMany('App::Comment'); } }Admin.phpclass Admin extends Model { protected $guarded = []; public function complaint() { return $this->hasOne(Complaint::class); } public function comments() { return $this->hasMany('Comment::class'); } }ユーザビリティについて
管理システムやフォーム入力についてのユーザビリティを調査しました。
フォームや投稿でのバリデーションの結果が、サーバーから返答が来てからまた編集をさせられるというのがユーザーにとって一番イライラくるそうです。
それを解決するためには、フロント側でユーザーが入力最中に何がダメかを表示してあげる機能があると便利だと思いました。
管理システム側も同様で、仕事の効率化のためになるべく悪いリクエストはフロント側で拒否したほうが良いと思いました。
クレームの対応状態の更新をページがリアルタイムではできないので、websocketを使うのもありかと思いました。
おわり
月曜日からコードをしていこうと思います。この設計が上手くいかなければ、また記事を書きます。最後までありがとうございました。
- 投稿日:2020-01-24T11:54:29+09:00
EloquentCollectionを活用するメモ
概要
モデルをget()後に加工しなければならないのためのメモ。
eagerロードの制約を加えた値は動的プロパティを使わないと受け取れないので、コレクションを活用して加工など行う必要がある。
備忘録。laravel 6.X
リレーション先の先の条件
fillter()を使う
ビューでクエリを行わずに、制約をかけた後のデータを渡すとスマートで安心する。
// ある記事に紐づく、公開中の評価がついた公開中のコメントが欲しい // 制約をかける $article->load([ 'comments' => function ($query) { $query->where('is_public', 1); }, 'comments.ratings' => function ($query) { $query->where('is_public', 1); } ]); $commnts = $article->comments->filter(function ($comment, $key) { // 動的プロパティでアクセス return $comment->ratings->count() > 0; });reject()
除去する条件を入れる
$commnts = $article->comments->reject(function ($comment, $key) { return $comment->ratings->count() === 0; });コレクションを合成する
concat()で他のモデルから取得したget()と合成する
ちなみに中身はコレクションをforeachで回して、push()しているだけのようです。
なので$articles = collect(\App\Models\Article::public()->take(3)->get()); $concat = $articles->concat( \App\Models\Service::query()->take(5)->get() );slice()を使ってページング
Illuminate\Pagination\LengthAwarePaginato
はページング対象引数をコレクションで取ることが出来るので、合成後のコレクションで独自にページネーション出来る。$offset = ($page * $limit) - $limit; $slice = $concat->slice($offset, $limit); $paginate = new LengthAwarePaginator($slice, count($concat), $limit, $page);(仕様に従ってみましたが、これはsliceせずにどうにかならないんでしょうか....)
追記
@nunulk さんにコメント頂き追記
forPage()を使ってスマートになりました。$paginate = new LengthAwarePaginator($concat->forPage($page, $limit), count($concat), $limit, $page);
- 投稿日:2020-01-24T09:56:52+09:00
Laravelで非同期チャットアプリを作ろう(2)
はじめに
この記事では、片方がコメントを送信したら、もう片方の人はリロードしなくても、コメントが表示されるチャットアプリを作っていきたいと思います。
完成物
ソースコード:https://github.com/Alesion30/ChatApp
非同期通信とは
非同期通信とは、ネットワークなどでつながれているコンピュータ間で、送信者のデータ送信タイミングと受信者のデータ受信タイミングを合わせずに通信を行う通信方式のこと。
日常生活の例でいうとこんな感じ。
同期通信
非同期通信
同期通信の場合は、リクエストを送信したらリロードが入り、他の処理を受け付けなくなるが、非同期通信の場合は、リクエストを送信しても、リロードが入らず別の処理を送信することもできる。
方針
- APIを作る。(URLにアクセスしたら、jsonデータを返すやつ)
- JavaScriptで、APIを叩きjsonデータを取得する。
- ループ処理を用いてjsonデータを、bladeファイルに埋め込む。
APIを作る
HomeController.phpにjsonを返すgetData()という関数を定義する。
app/Http/Controllers/HomeController.phppublic function getData() { $comments = Comment::orderBy('created_at', 'desc')->get(); $json = ["comments" => $comments]; return response()->json($json); }web.phpに、getData()を登録する。
routes/web.phpRoute::get('/result/ajax', 'HomeController@getData');http://localhost/ChatApp/public/result/ajax にアクセスすると、jsonデータが返ってくる。
ajaxを使って、jsonを取得する
ajaxを使えるようにするために、app.blade.phpのheadの部分でスクリプトを読み込む。
resources/views/layouts/app.blade.php<!-- Scripts --> <script src="{{ asset('js/app.js') }}" defer></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>これはajaxの基本形である。dataTypeには、jsonだけでなく、csvやhtmlも指定できる。
$.ajax({ url: "result/ajax/", dataType: "json", success: data => { // 成功時の処理 }, error: () => { // エラー時の処理 } })public/jsフォルダにcomment.jsを新規作成する。5秒ごとに、jsonデータを取得している。
public/js/comment.js$(function() { get_data(); }); function get_data() { $.ajax({ url: "result/ajax/", dataType: "json", success: data => { console.log(data); }, error: () => { alert("ajax Error"); } }); setTimeout("get_data()", 5000); }app.blade.phpのbodyタグの一番下に、
@yield('js')
を入れる。resources/views/layouts/app.blade.php<main class="py-4"> @yield('content') </main> </div> @yield('js') </body>home.blade.phpの一番下で、comment.jsを読み込む。
resources/views/home.blade.php@section('js') <script src="{{ asset('js/comment.js') }}"></script> @endsectionhttp://localhost/ChatApp/public/home にアクセスして、検証ツールを使うと、5秒ごとにjsonデータが送られてきていることがわかる。
comment.jsのsuccessの処理を以下のように書き換えてみる。
public/js/comment.jssuccess: data => { console.log(data.comments); for (var i = 0; i < data.comments.length; i++) { console.log(data.comments[i].name); console.log(data.comments[i].comment); } },ループ処理を用いてjsonデータを、bladeファイルに埋め込む。
home.blade.phpでコメントを表示している部分を書き換える。
resources/views/home.blade.php<div class="chat-container row justify-content-center"> <div class="chat-area"> <div class="card"> <div class="card-header">Comment</div> <div class="card-body chat-card"> <div id="comment-data"></div> </div> </div> </div> </div>JavaScriptで、
<div id="comment-data"></div>
の中にコメントを埋め込んでいく形になる。comment.jsのsuccessの処理を以下のように書き換える。
public/js/comment.jssuccess: data => { $("#comment-data") .find(".comment-visible") .remove(); for (var i = 0; i < data.comments.length; i++) { var html = ` <div class="media comment-visible"> <div class="media-body comment-body"> <div class="row"> <span class="comment-body-user" id="name">${data.comments[i].name}</span> <span class="comment-body-time" id="created_at">${data.comments[i].created_at}</span> </div> <span class="comment-body-content" id="comment">${data.comments[i].comment}</span> </div> </div> `; $("#comment-data").append(html); } },http://localhost/ChatApp/public/home にアクセスして、コメントを追加するとうまく反映している。
- 投稿日:2020-01-24T09:09:08+09:00
Laravel5.8 サブクエリー(leftJoinSub)
説明
QueryBuilderがかなり使えるので、メモです。
サブクエリーもかんたんに書けます。サブクエリーで合計をとります。
ユーザーへのメッセージがあって、それに対していいねをする想定のテーブル構成です。
以下、カラムは最小限で、適当です。
あと、正規化してない部分もあります。テーブル
1.users :ユーザーテーブル
論理名 物理名 id PK company_id 企業ID name 名前 2.user_messages :ユーザーメッセージテーブル
論理名 物理名 id PK(メッセージのID) user_id ユーザーテーブルのID(貰った人) message メッセージ 3. user_message_likes :ユーザーメッセージへのいいねテーブル
論理名 物理名 id PK(いいねID) company_id 企業ID user_message_id ユーザーメッセージテーブル のID メッセージと、メッセージ毎のいいね数を取得する
<?php // ユーザーメッセージIDでグループ化し、メッセージ毎のいいね数を取得するサブクエリー $like_count = DB::table('users_message_likes') ->select('user_message_id', DB::raw('count(user_message_id) AS like_cnt')) ->groupBy('user_message_id'); // ===== // メッセージと、いいね数を取得する処理 // ===== $messages = DB::table('user_messages') // ユーザー情報取得用のjoin ->join('users', 'users.id', '=', 'users_messages.user_id') // いいね数取得用のサブクエリー(所属企業を絞る) ->leftJoinSub($like_count, 'count_table', function ($join) use ($company_id) { $join->on('users_messages.id', '=', 'count_table.user_message_id')->where('count_table.company_id', '=', $company_id); }) ->orderBy('users_messages.id', 'DESC') ->where(['users.company_id', '=', $company_id]) ->select([ 'users.name AS name', 'user_messages.id AS id', 'user_messages.message AS message', 'count_table.like_cnt AS like_cnt', ]) ->get(); // レコードあるか if (count($messages) == 0) { // 抜けるなどの処理 } // レコード存在時は、ループして取得する foreach ($messages as $message) { // データ操作 }追伸
結局、カウントは集計をテーブルに持ったので使いませんでしたが、
覚えておいたら使えると思います。