--> -->
* 今日のLaravel [#ta529ca4] これは、[[2018アドベントカレンダー記事:https://adventar.org/calendars/3811]]で、Laravel(5.4)を使った開発の中で気づいた事を毎日一つメモしていくものです。 #contents ---- ** 2018/12/5 resourceのroute名の変更方法 [#k2619b38] 開発を進めていくと、routeの記述がどうしても長大になりがちな中で、resource定義は一行で複数の挙動が定義できて、しかもroute名も自動で定義してくれてとても助かります。~ しかし、例えば以下のように別々のディレクトリ内で同じ定義名を使いたい場合があります。 #code(php){{ Route::resource('user/regist', 'UserHogeController'); Route::resource('product/regist', 'ProductHogeController'); }} この場合、route名はどちらも「regist.index」等となってしまい、別々に識別することができなくなってしまいます。~ ~ この問題の解決方法として、[[マニュアル:https://readouble.com/laravel/5.4/ja/controllers.html#restful-naming-resource-routes]]では以下のように再定義できると書かれています。 #code(php){{ Route::resource('photo', 'PhotoController', ['names' => [ 'create' => 'photo.build' ]]); }} しかし、アクションがindex,create,store,show,edit,update,destroyと7つもあるのに、いちいち書いていたらroute定義が無駄に長くなってしまいます。 そこで、resource()のソースを確認すると、&inlinecode{Illuminate\Routing\ResourceRegistrar::getResourceRouteName()};で以下のように作成していました。 #code(php){{ if (isset($options['names'])) { if (is_string($options['names'])) { $name = $options['names']; } elseif (isset($options['names'][$method])) { return $options['names'][$method]; } } (後略) }} stringも許可しています。なので、以下の通り指定すると7つのアクション全てを書き換えてくれます。 #code(php){{ Route::resource('user/regist', 'UserHogeController',['names'=>'user-regist']); Route::resource('product/regist', 'ProductHogeController',['names'=>'product-regist']); }} 便利(^^)~ ~ ---- ** 2018/12/6 apiでセッションを使おうとするとformと両立できない [#o0b28b82] LaravelではAPIアクセスはapi.phpで定義できますが、ステートレスなアクセスを前提としているため、セッションを使うことができません。((LaravelでセッションIDで認証状態をチェックするステートフルなAPIを使う&br;https://qiita.com/pinekta/items/d10c8374b1a3003cd952))~ 参考ページの通りに対応すればセッションが有効になりますが、以下の手順で操作した場合にCSRFエラーになります。~ + フォームの中でボタンを押すとajaxを使用してAPIアクセス + フォームからpostでsubmit #ref(csrf.png)~ ~ 想像ですが、APIアクセス時にセッション保持しているCSRFトークンが書き換わってしまい、フォームに埋め込まれたトークンと一致しなくなってしまうと考えられます。~ ~ 私の場合はやむなく無認証にしましたが、どうしても認証したい場合は、アクセストークン方式にする必要があるのではないかと思います。 ~ ---- ** 2018/12/7 php5とphp7での配列を使ったメソッド呼び出しの挙動の違い [#bfdade1e] 以下のコードにおいて、php5とphp7で挙動の違いがありました。 #code(php){{ <?php class test { public function __construct() { echo "test!\n"; $method[0] = "test2"; $this->$method[0](); } private function test2() { echo "test2\n"; } } new test(); }} - php5(5.6.30)の場合 >php test.php test! test2 - php7(7.1.24)の場合 >php test.php test! PHP Notice: Array to string conversion in F:\test.php on line 8 PHP Stack trace: PHP 1. {main}() F:\test.php:0 PHP 2. test->__construct() F:\test.php:17 : PHP Fatal error: Uncaught Error: Function name must be a string in F:\test.php:8 どうやら変数が配列の場合、php7だと「$this->$method」で一旦区切って解釈するようです。~ ~ 解決方法は以下の通り。 - $this->$method[0](); + $this->{$method[0]}(); これでphp5でも7でも動作するコードになりました。 ~ ---- ** 2018/12/8 WYSWYGエディタには要注意 [#j53c17f7] WYSWYGはWhat You See What You Getの略で、直訳すると見たままの物が得られる、という意味です。~ そんなの当たり前じゃん、と言っているそこの若者! [[これが誕生した頃は画期的:https://ja.wikipedia.org/wiki/WYSIWYG#%E6%84%8F%E5%91%B3]]だったのですよ(Appleが広めた)。~ ~ まあそんなことはいいとして、フリーで使えるWYSWYGエディタは各種あります。((商用でも利用できる、イケてるWYSIWYGエディタ7選 2017年版&br;https://engineer.blog.lancers.jp/2017/12/wysiwyg_editor_best_7/))~ 私が試したのは[[Trumbowyg:https://alex-d.github.io/Trumbowyg/]]。簡単にこんなTEXTAREAが得られます。~ #ref(trumbowyg1.png)~ ~ 使い方はこんな感じ。~ #code(php){{{ {{Form::textarea('description')}} {{Html::style(asset('trumbowyg/dist/ui/trumbowyg.min.css'))}} {{Html::script(asset('trumbowyg/dist/trumbowyg.min.js'))}} {{Html::script(asset('trumbowyg/dist/langs/ja.min.js'))}} <script language="JavaScript"> $('textarea').trumbowyg({ lang: 'ja', }); </script> }}} &size(10){(スクショはプラグインを幾つか入れてます)};~ ~ このエディタ、画面上に得るべき物を表示しているということは、HTMLタグをレンダリングしているということになります。このTrumbowygは生HTMLも書けるので、試しにJavascriptタグを埋めてみます。~ #ref(trumbowyg2.png)~ ~ そして生HTMLモードを解除してみると・・・~ #ref(trumbowyg3.png)~ ~ うーん、ダメだこりゃ。これではユーザーが自由にこのサーバー上でJavascriptを動かせちゃうということです。~ 生HTMLは書けないモードにして使う必要がありそうです。~ Trumbowygはボタンのカスタマイズができますので、例えばこんな感じです。~ #code(php){{ <script language="JavaScript"> $('textarea').trumbowyg({ lang: 'ja', btns: [ ['undo', 'redo'], // Only supported in Blink browsers ['formatting'], ['strong', 'em', 'del'], ['superscript', 'subscript'], ['link'], ['insertImage'], ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], ['unorderedList', 'orderedList'], ['horizontalRule'], ['removeformat'], ['fullscreen'], ] }); </script> }} ~ ただし、これでもブラウザのJavascriptを無効にして、<script>タグ書いて、再度有効にすればやはり実行できます。うーん悩ましい・・・。~ ~ 追記: ぬれぎぬでした・・・→[[#q4ca3ee1]] ---- ** 2018/12/9 Laravelのファイルストレージ(S3)を使う [#b40b240a] 一番ひっかかっのはS3の設定です(^^;)。当初S3の設定が良く分からなかったのですが、こちら((超簡単!LaravelでS3を利用する手順&br;https://qiita.com/tiwu_official/items/ecb115a92ebfebf6a92f))に非常に詳しく書かれています。感謝!~ S3のバケットを作るだけではダメで、S3とは独立した設定でユーザーを作るのが必要でした。AWSの基本?(^^;)~ 但し、上記の方法はユーザーの認証情報とバケットを結びつけていないため、このユーザーのS3には全てアクセスできてしまうと思われます。実際には当該バケットに限定した権限設定が必要でしょう。~ ~ まずはコードの確認でlocalに保存してみます。~ - View #code(PHP){{{ {{Form::open(['files' => true])}} {{Form::file('file')}} {{Form::submit()}} {{Form::close()}} }}} いや、まあこれだけじゃないでしょうけど(笑)、Form::open()に&inlinecode{'files' => true};を付けるのを忘れずに。~ - Controller #code(php){{ $file = $request->file('file'); $filename = $file->getClientOriginalName(); $path= Storage::disk('local')->putFile('hoge', $file); }} hogeはサブディレクトリ名です。特に必要がなければ'/'。~ すると、&inlinecode{storage/app/hoge};の下に、ユニークなファイル名でファイルが保存されます。(Oc4GsSGFIe3NkjnUUttt4mnaRtpKklQhv5ZArdMq.txt みたいな感じ)~ 従って、元のファイル名は&inlinecode{getClientOriginalName()};を使って別途保存しておく必要があります。~ ~ 次に、S3に保存してみます。~ これは簡単で、コードを以下のように変えるだけです。(もちろんS3の設定、&inlinecode{.env};の設定、&inlinecode{league/flysystem-aws-s3-v3};の導入は済んでいる前提です) #code(php){{ $path= Storage::disk('s3')->putFile('hoge', $file); }} ~ しかし、ここで問題が起きました(ネタ、来た(笑))~ 以下のようなエラーが発生しました。~ #ref(curlsslerr.png)~ ~ 要約すると、ローカルの証明書が無いからSSLアクセスができない、ということです。調べるとWindowsのCURLはデフォルトで証明書が無いためSSLアクセスがエラーになるということが分かりました(S3関係ないやん・・・(笑))。~ 検索するとCURLにオプションを付ける対策などが出てくるのですが、LaravelFWをいじることになるので却下。~ こちら((cURL error 60: SSL certificate in Laravel 5.4&br;https://stackoverflow.com/questions/42094842/curl-error-60-ssl-certificate-in-laravel-5-4))のページにある方法で解決しました。 ~ + [[http://curl.haxx.se/ca/cacert.pem]]から証明書をダウンロード。 + 適当なディレクトリに保存。 + php.iniで&inlinecode{curl.cainfo = "C:\php5.6.37\cacert.pem"};のように指定 + Apache再起動 実行後、S3のコンソールでファイルが確認できれば成功です。~ ちなみに、localからS3に切り替える場合、いちいちコードを修正しなければいけないのでしょうか? もちろんそんなことはありません。&inlinecode{Storage::disk()};のソースを見ると、 #code(PHP){{ public function disk($name = null) { $name = $name ?: $this->getDefaultDriver(); return $this->disks[$name] = $this->get($name); } : public function getDefaultDriver() { return $this->app['config']['filesystems.default']; } }} のようになっています。つまり&inlinecode{Storage::disk()};と引数無しにしておけば、.envを変更するだけで切り替えられるようになります。(もちろん切り替え時のデータ移行は必要ですけど) ~ ---- ** 2018/12/10 phpの5と7.1と7.2、MySQLの5.5と5.6 [#h6d99c11] 私は複数箇所に開発環境があるのですが、片方は本番と同じphp5.6+MySQL5.7です。もう一方はなるべく新しい環境でも動作することを担保するため、php7+MySQL8にしてみました。そこで分かったことを2つ(3つ?)。~ + php7の環境でcomposer updateしてはいけない~ updateするとLaravelのライブラリが色々上がります。その環境ではもちろん動くのですが、php5の環境で動かそうとした時に問題が発生します。php7で新たに追加された機能(戻り値の型の宣言など((PHP 5.6.x から PHP 7.0.x への移行&br;http://php.net/manual/ja/migration70.new-features.php#migration70.new-features.return-type-declarations)))でエラーになるのです。~ 従って、composer updateは必ずphp5.6の環境で行う必要があります。~ もし誤ってphp7で上げてしまってもphp5.6環境でupdateすれば戻ります。~ ~ + count(null)がエラーになる~ php7.1では問題ありませんが、php7.2ではcount(null)がNOTICEを吐くようになりました((PHP7.2のcountにハマった話&br;https://qiita.com/masaki-ogawa/items/1671d110b2286ececd09))。LaravelではExceptionが発生します。~ 厳密に型を考えてコードを書いている人(なんてphpで居るの?)は問題無いと思いますが、私はそこかしこでやってしまっているので、php7.2は断念しました。~ ~ + MySQL8ではstrictのモードが違う MySQLはバージョンが変わるとデフォルトの設定があれこれ変わることで有名(?)です。~ MySQL8とLaravelの環境では、こんなエラーが出ました。~ #ref(strict.png)~ 簡単に回避するには、database.phpの設定でstrictをfalseにしてやると出なくなります。((Laravel5 MySQL8.0 NO_AUTO_CREATE_USERのSQL_MODEエラー対策&br;https://qiita.com/ucan-lab/items/2a482a9537dcc5daeb97))~ でも本当はMySQLの環境を合わせた方が良いでしょうね。((Default laravel's strict mode isn't compatible with MySQL 8.0 (NO_AUTO_CREATE_USERS)&br;https://github.com/laravel/framework/issues/23970)) ~ ---- ** 2018/12/11 WYSWYGエディタの使い方 [#q4ca3ee1] 12/8にWYSWYGエディタに生HTMLが書けてしまう危険性について書きました。~ その後色々調べてみると、どうやら使い方に問題があることが分かりました。エディタのせいにしてしまってごめんなさいm(_ _)m~ ~ 前回は&inlinecode{{{Form::textarea()}}};を使っていたのですが、これだとJavascriptを無効にしてもテキスト入力ができてしまい、脆弱性が生まれます。~ Javascriptが無効の時は入力も無効になるよう、以下のようにすればOKです。 #code(php){{{ <div id="description">{!!$contents['description']!!}</div> : <script language="JavaScript"> $('#description').trumbowyg({ lang: 'ja', }); </script> }}} サンプルもこのようになっているんですが、一部のドキュメント((https://alex-d.github.io/Trumbowyg/documentation/#prefix))はtextareaを使っちゃってるんですよね。これ危険では・・・(^^;)~ ~ ちなみにsubmitの時は、divでは値が飛ばないので一工夫必要です。 #code(php){{{ <script language="JavaScript"> function doSubmit(){ let desc = $('#description').html(); // 要素追加 let elm = document.createElement('input'); // 属性を設定 elm.setAttribute('type', 'hidden'); elm.setAttribute('name', 'description'); elm.setAttribute('value', desc); // 要素を追加 let frm = document.getElementById('formEdit'); frm.appendChild(elm); frm.submit(); } </script> : {{Form::submit('更新',['onclick'=>'doSubmit();'])}} }}} ~ ---- ** 2018/12/12 WindowsのMultiByteファイル名とエンコード [#p30aeebb] 今日はディレクトリ内にあるファイルを拾ってSQL文にかけるという[[コマンドライン:https://readouble.com/laravel/5.4/ja/artisan.html#generating-commands]]処理を書いていました。~ サンプル的に書くと以下のような感じです。~ #code(php){{ public function handle() { $list = scandir('F:\\'); foreach($list as $filename) { echo $filename."\n"; DB::table('migration')->where('filename',$filename)->count(); } } }} 2カ所ある環境のうち、1カ所(環境A)では問題無く動いたのですが、もう一方(環境B)では何故かエラーになりました。どちらもWindows10Proです。~ > SQLSTATE[HY000]: General error: 1267 Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,COERCIBLE) for operation '=' (SQL: select count(*) as aggregate from `migration` where `name` = 20181202_instにstatus追加.txt) < DBのcollationはutf8_general_ciです。何故かWindowsのファイル名はutf8mb4_unicode_ciとなっているようです。ちなみにソースはUTF8で書かれています。~ ~ 環境Aでこのまま動いたのが謎ではあるのですが、普通に考えるとWindowsはSJIS環境ですので、以下の変換を入れてみます。~ $filename = mb_convert_encoding($filename,mb_internal_encoding(),(PHP_OS=='WINNT'?'SJIS':'UTF-8')); すると、エラー無く動作するようになりました((この変換処理が環境AやLinux環境で正常に動作するかは未確認です))。(DBへの照会も正しくされていました)~ ~ 何故同じWindows10でこのような差が出るのかは謎です。思い当たる違いと言えば、環境Aは今年のWindows10クリーンインストールで、環境BはMSDNのWindows10 1151からのアップデートということぐらいですが・・・。なぜ?(笑) ~ -- 2018/12/14追記~ 改修コードは環境Aではやはり文字化けしてしまったのですが、そういえば環境AはPHP7、環境BはPHP5という違いもありました。そもそもphp.iniのencodeの設定も比較してみる必要があります。結論はもう少し先送りに。。~ ~ ---- ** 2018/12/13 ValidatorのURLは何を見ているのか? [#c0c9888d] バリデーションに&inlinecode{url};を使っている処理をテストしていて妙な事に気づきました。~ &inlinecode{https://localhost};は通るのに、&inlinecode{https://localhost2};はバリデーションエラーになるのです。~ まさか実在するサーバーをチェックしているのか? と思って&inlinecode{https://test.example.jp};とか入れてもエラーにはなりません。~ では一体何をチェックしているのか?~ ~ ソースを見ます。~ URLチェックは&inlinecode{Illuminate\Validation\Concerns\ValidatesAttributes->validateUrl()};で行われています。~ この中には以下のような長大な正規表現が書かれており、これでチェックしている事が分かります。~ #code(php){{ $pattern = '~^ ((aaa|aaas|about|acap|acct|acr|adiumxtra|afp|afs|aim|apt|attachment|aw|barion|beshare|bitcoin|blob|bolo|callto|cap|chrome|chrome-extension|cid|coap|coaps|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-playcontainer|dlna-playsingle|dns|dntp|dtn|dvb|ed2k|example|facetime|fax|feed|feedready|file|filesystem|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|ham|hcp|http|https|iax|icap|icon|im|imap|info|iotdisco|ipn|ipp|ipps|irc|irc6|ircs|iris|iris.beep|iris.lwz|iris.xpc|iris.xpcs|itms|jabber|jar|jms|keyparc|lastfm|ldap|ldaps|magnet|mailserver|mailto|maps|market|message|mid|mms|modem|ms-help|ms-settings|ms-settings-airplanemode|ms-settings-bluetooth|ms-settings-camera|ms-settings-cellular|ms-settings-cloudstorage|ms-settings-emailandaccounts|ms-settings-language|ms-settings-location|ms-settings-lock|ms-settings-nfctransactions|ms-settings-notifications|ms-settings-power|ms-settings-privacy|ms-settings-proximity|ms-settings-screenrotation|ms-settings-wifi|ms-settings-workplace|msnim|msrp|msrps|mtqp|mumble|mupdate|mvn|news|nfs|ni|nih|nntp|notes|oid|opaquelocktoken|pack|palm|paparazzi|pkcs11|platform|pop|pres|prospero|proxy|psyc|query|redis|rediss|reload|res|resource|rmi|rsync|rtmfp|rtmp|rtsp|rtsps|rtspu|secondlife|service|session|sftp|sgn|shttp|sieve|sip|sips|skype|smb|sms|smtp|snews|snmp|soap.beep|soap.beeps|soldat|spotify|ssh|steam|stun|stuns|submit|svn|tag|teamspeak|tel|teliaeid|telnet|tftp|things|thismessage|tip|tn3270|turn|turns|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|videotex|view-source|wais|webcal|ws|wss|wtai|wyciwyg|xcon|xcon-userid|xfire|xmlrpc\.beep|xmlrpc.beeps|xmpp|xri|ymsgr|z39\.50|z39\.50r|z39\.50s)):// # protocol (([\pL\pN-]+:)?([\pL\pN-]+)@)? # basic auth ( ([\pL\pN\pS-\.])+(\.?([\pL]|xn\-\-[\pL\pN-]+)+\.?) # a domain name | # or \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address | # or \[ (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) \] # an IPv6 address ) (:[0-9]+)? # a port (optional) (/?|/\S+|\?\S*|\#\S*) # a /, nothing, a / with something, a query or a fragment $~ixu'; return preg_match($pattern, $value) > 0; }} 2行目はプロトコル(こんなにあるの!?(笑))、3行目はURLの前に付けるBASIC認証、ドメイン部分は5行目です。~ 完全には読み解けていませんが、簡単に言うと数字で終わるものはNG。但し2センテンス以降に「xn--」の後に続く文字列が数字で終わるのはOKです。((http://php.net/manual/ja/regexp.reference.unicode.php)) ○localhost、×localhost2、○local.xn--abc2、×xn--abc2 ちなみにこの「xn--」は国際化ドメイン名を7bit ASCIIで表すときの開始文字列です。~ ~ 正規のリモートURLで数字で終わるものは確かに無いでしょうが、ホスト名だけ指定する場合はあり得ますので、パターンとしては通してくれても良かったんじゃないかなと思います。~ まあ、実用上問題は無いでしょうけど。(イントラで運用するような特殊な環境では問題が出る場合無いとは言えない(笑))~ ~ ---- ** 2018/12/14 Laraveで使えるベイジアンフィルタ [#h8db4739] 蓄積された情報を元に、新たな問に対して何が尤もらしい答えかというのを導き出したいとします。今時的にはAIなのかもしれませんが、ちょっと敷居が高い、そこで便利なのがスパムフィルタとして普及したベイジアンフィルタです。~ 「ベイジアンフィルタ? 古っ」と思われるかもしれませんが、軽くてシンプルでそれでいて割と「当たる」し学習すれば精度も上がるので、今回の目的には十分です。&size(10){(某@snowandtweetという所でも使用していますが、日本語の文章をかなり適切に判定してくれます)};~ ~ このベイジアンフィルタは、単語の組み合わせや出現率を数値化して、その文章が何について語っているのかを教師データに基づいて分類します。スパムフィルタは「スパムか」「スパムじゃ無いか」の2分類しかしていませんが、アルゴリズムとしては教師データさえ与えれば何分類でもできます。~ ~ さて、いざ良さげなフィルタを探そうとしたとき、「Laravelで使える」という点ではここで検索するのが速いです。~ ~ ■Packagist~ https://packagist.org/search/?q=bayes ~ ~ 似たキーワードでもひっかかってくるので全てが目的のパッケージではありません。上から16個ぐらいが探している物です。~ 一番人気の&inlinecode{camspiers/statistical-classifier};は何故か手元の環境に入りませんでした。古すぎるのかも知れません。~ &size(10){ベイジアンフィルタ自体流行ったのが7,8年前なので、パッケージも全体的に古いです};~ ~ 日本人が作ったっぽい&inlinecode{tsuzukit/laravel-naive-bayes};というのもあるのですが、処理の中身がカラでした。消しちゃった?~ 手元の環境はPHP5.6なので、7以上を要求するものはNGです。~ また、継続的に学習するので、学習結果をDBに保存してくれないものもNGです。~ ~ と言いながら探していくと、該当する物が無くなってしまったので、二番人気でソースも非常にシンプルな&inlinecode{fieg/bayes};が良さそうだということになりました。これをベースに自分でごにょごにょしてみようと思います。 ~ ---- ** 2018/12/15 PHP Conference 2018 [#g82e1478] 20年近くPHPをやってきて、初めて行ってみました、PHP Conference。~ 会自体はとても大きく、以下のタイムテーブルのような講演が行われていてとても見切れないので、Track1で開催されていた5講演について感想を書いてみたいと思います。~ #ref(timetable.png) ~ + Opening(主催者さん)~ PHPは多くの人に古くから使われていて、ユーザーには中堅が多くなってきている。若手に使って貰えるよう力を入れている、という話でした。ただカンファレンス出席者を眺めてみましたが、30代前半以下と思われる(大手企業で言う)若手が殆ど(95%ぐらい)で、40代以上と思われる人はほぼ居ませんでした。ユーザーは多いけどこういう所に来るのは若い人ばかり、ということでしょうか?~ 確かに家庭があったら来づらいかも。~ ~ + PHPのいまとこれから(PHPコミッターさん)~ PHP5, 7のユーザー数比の話や、EOL(End Of Life)が今月来ること、PHP7の改善点、php7.4の新機能の話など。特にPHP5と7では言語としての速度は2倍程度上がっており、使用メモリはざっくり1/5ぐらいになっているとのことで、EOLの問題と絡めて早めに移行が必要だと感じました。~ ちなみにPHPのコミッターは世界に200人(だったかな)程度おり、うち日本人は15人ほどと思われるとのことでした。~ ~ + 大規模PHPプロジェクトでPHPUnitを3世代アップグレードするためにやったこと(メルカリさん)~ PHPUnitを4→5→6に上げたという話。テストコードは膨大なのと、正式な業務としてでは無く片手間でやったということもあり、1年ぐらいこつこつ実施したため、PHPUnitをいきなりバージョンアップできず(他のテストが軒並みNGになる)色々工夫したという話でした。~ 方法としては、PHPUnitが4のまま4でも5でも動くテストコードに書き直し、全部終わったところでPHPUnitを5に上げる、という事を繰り返したということです。両方で動くために小技を使ったりしており、若干汚いテストコードが残りますが、まあそれはそれなのでしょう(テストコードだし)。~ ほぼ担当者一人の熱意で成し遂げられた印象で、(PHPUnit5→7の効果がどれだけあるのかわかりませんが)結局こういう成果は一担当者のパーソナリティに支えられるんだなと感じました。(大きな会社の大きなプロジェクトでもままあります)~ ~ +2018年のPHP構成を考える(pixivさん)~ pixivでのPHPの活用方法やその知見の共有の話かと思いきや、ぐぐれば分かるような「こんな技術があるよ」という紹介でした。話もとりとめが無く、スピーカーの方の個人的な感想をつらつらと話すような内容。厳しいようですが、もしpixiv内のPHP第一人者がスピーカーの方だとしたら、社内でPHPは殆ど戦力と考えられていないのではないでしょうか(スポンサーなのに・・)。~ ~ + 安全なWebアプリケーションの作り方2018(徳丸さん)~ 今日はこれを聞きに来たと言っても過言ではありません。~ 内容はそれなりにマニアックでしたが、実演を伴いながら知りたいことを全部教えてくれ、喋りもさすがで時間を忘れて聞いていました。もっと聞きたかったです(笑)。尤も、今年の新脆弱性トレンドに絞った内容で、Webセキュリティという非常に範囲の広いテーマをカバーするにはやはり徳丸本を読まないといけないなーと思いました。~ 知識的な面で言えばわざわざ足を運ばなくても、という部分も無いではないですが、最新の動向の空気に触れるという意味で意義のある一日でした。 ~ ---- ** 2018/12/16 CORS要求(他サイトから呼び出される処理を作る) [#u83078f3] 他サイトからAPIなりページのloadなりで呼び出されるサービスを作るとぶつかるエラーがあります。それが[[CORS:https://developer.mozilla.org/ja/docs/Web/HTTP/CORS/Errors]]です。~ JavaScript Consoleにはこんなメッセージが出ます。~ #ref(corserror.png)~ ~ エラーの理由は「[[CORS header 'Access-Control-Allow-Origin' missing:https://developer.mozilla.org/ja/docs/Web/HTTP/CORS/Errors/CORSMissingAllowOrigin]]」ですので、ググると&inlinecode{barryvdh/laravel-cors};を入れると良いことが分かります。~ ということで、[[こちら:https://github.com/barryvdh/laravel-cors]]の記述に従ってインストール及び設定すればOKです。~ ~ 今回作った処理は全体的に他サイトから呼ばれる想定なので、middlewareに入れてwebでもapiでも&inlinecode{Access-Control-Allow-Origin};ヘッダーが入るようにしました。動作確認でも、無事エラーも出ず、処理も動くようになりました。めでたしめでたし。~ ~ ---- ** 2018/12/17 fieg/bayesをMySQL対応する [#kdd2bd30] 12/17分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20181217]] ~ ---- ** 2018/12/18 ateliee/mecabをmecabにパスを通さなくても良くするのとfieg/bayesのmecab対応 [#vb58fb94] 12/18分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20181218]] ~ ---- ** 2018/12/19 JavaScriptをBlade+PhpStormで使って書く [#j96e79ff] 12/19分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20181219]] ~ ---- ** 2018/12/20 JavaScriptでJavaScriptを読み込む [#l501d74b] なんの事かと言いますと、以下のようにJavaScriptでDOMに生成したHTMLでJavaScriptを実行する場合、そのまま読み込んだHTMLに書かれていても実行されません。 #code(php){{ let div = document.createElement('div'); div.setAttribute("id","divid"); let bdy = document.getElementsByTagName("body"); let elm=bdy.item(0); elm.appendChild(div); htmlLoad("//www.example.jp/hoge.html", "divid"); }} 6行目はid="divid"にurlで指定されたHTMLを読む関数があると思ってください。jQueryなら $("#divid").load("//www.example.jp/hoge.html"); とかです。~ ~ これだけだと、hoge.htmlの中にJavaSciprtが書かれていても実行されません。((innerHTMLに入れたコードの中にscriptタグがあっても実行されないのはなぜ?&br;https://ja.stackoverflow.com/questions/2756/innerhtml%E3%81%AB%E5%85%A5%E3%82%8C%E3%81%9F%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E4%B8%AD%E3%81%ABscript%E3%82%BF%E3%82%B0%E3%81%8C%E3%81%82%E3%81%A3%E3%81%A6%E3%82%82%E5%AE%9F%E8%A1%8C%E3%81%95%E3%82%8C%E3%81%AA%E3%81%84%E3%81%AE%E3%81%AF%E3%81%AA%E3%81%9C))~ ~ なので、注釈のサイト先で述べられている通り以下のようにする必要があります。 #code(php){{ (hoge.htmlを読み込んだ後の処理で) let elm= document.getElementById("divid"); var script = document.createElement('script'); script.src = "//www.example.jp/hoge.js"; elm.appendChild(script); }} &inlinecode{elm};は必ずしも&inlinecode{divid};につける必要はありませんが、&inlinecode{divid};の持ち物なので一応ここにしています。 ~ ---- ** 2018/12/21 Mockeyで部分的にmethodを差し替えてテストする [#bf892898] 12/21分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20181220]](URLは何故か1220ですが・・・) ~ ---- ** 2018/12/23 外部クラスの呼び出しをMockする [#o7d9d91c] 12/23分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20181223]] ~ ---- ** 2018/12/25 振り返って [#v4bca44c] ~今回初めてアドベントカレンダーに乗っかって毎日記事を書いてみました(本来の意図からは離れてますが)。 ~以前からちょっとしたことでもblogに残そうと思ってはいましたが、いざ書こうと思うと「このネタでわざわざ1記事?」と思って躊躇することも多かったので、とても良い機会になりました。と共に、毎日1ネタはさすがにきつかったです(最後書き切れなかった・・)。 ~ただ、書いてみて感じたのは意外にも「意外に書ける」でした。現在私はサーバー構築からDB設計、ネットワーク設計、セキュリティ対策、コーディング、試験まで幅広く行っています。すると毎日分からないことばかり。特に使い慣れた枯れた技術よりは今一番適当な技術を使っていこうと思うと、毎日調べ物ばかりです。 ~しかし毎日調べているとなかなか成果物が出来上がらず、「今週何やった?」的な心境になることもあります。ですがこのように日々の調査結果を記録として残しておけば、そういった虚無感(?)に苛まれることもありません。(それよりは休日に記事が進まないことが結構ストレスになった) ~記事にする以上は単なる日報ではダメで、ちょっとググったぐらいでは分からない、試行錯誤の結果導かれた解を整理して記録する必要があります。調べ物が中途半端に終わらず、一区切りして気持ちよく眠れる所まで辿り着けたのも、アドベントカレンダーの効果ではなかったかと思います。 ~アドベントカレンダーはもう終わりですが、今後も稼働日には1日1ネタを課して学んだことをコツコツ共有していこうと思います。 ---- ** 2019/1/12 Collectiveのselectでoptionに属性を指定する [#k1c5e517] Advent Calenderは終わっても継続(笑)。~ 1/12分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20190112]] ~ ---- ** 2019/1/30 単体テスト時の「redirect()->back()」の検出 [#r81b0d9f] 久々に追記です。~ 単体テスト(UT)では、GETやPOSTがリダイレクトされた場合、以下のようにリダイレクト先をチェックすることができます。~ #code(php){{ $response = $this->get(route('route-name')); $response->assertStatus(302); $response->assertRedirect(route('redirect-route-name')); }} ところが、&inlinecode{redirect()->back()};で戻った場合、UTではそもそも元になるページが無いため、リダイレクト先URLは「/」になってしまいます。これでは実動作と異なりますし、暗黙の了解的に「/」をチェックするのも気持ち悪いです。~ ~ そういう場合は、以下のようにGET/POST(/PUT/DELETE)すれば、指定したURLへback()してくれます。~ #code(php){{ $response = $this->post(route('resource.store'),[data...],['HTTP_REFERER'=>route('resource.create')]); $response->assertStatus(302); $response->assertRedirect(route('resource.create')); }} いじょ。 ~ ---- ** 2019/01/13 insertGetIdはスレッドセーフなのか? [#veef0936] 2019/1/13分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20190113]] ~ ---- ** 2019/02/11 resrouceで確認画面を作成する [#k7329664] 2019/2/11分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20190211]] ~ ---- ** 2019/02/17 ExcelVBAで出力したUTF-8はBOM付きだからPHPで読む際には除去が必要 [#xde74f10] 2019/2/17分は[[こちら:https://www.webdb.co.jp/~atsumi/skimemo/index.php?Laravel-20190217]] ~