20211012のPHPに関する記事は9件です。

PHPでnull判定関数(isset, empty, is_null)の違い

はじめに 実務で「変数に配列がセットされてるけど、空配列であることを判定」する必要がありました。 PHPでは配列の空判定が出来る関数としてisset, empty, is_nullがありますが、違いがよくわからずに苦労しました。 理解を深めるために違いをまとめたので記事にしました! 対象者 この記事は下記のような人を対象にしています。 駆け出しエンジニア プログラミング初学者 null?空文字?なにそれ美味しいの?な人 isset, empty, is_null...聞いたことあるけど、全部同じじゃないの?と思ってる人 PHPのnull判定関数(isset, empty, is_null)の違い早見表 まずは結論から。 値 if($var) isset empty is_null 1 $var=1 true true false false 2 $var=""; false true true false 3 $var="0"; false true true false 4 $var=0; false true true false 5 $var=NULL; false false true true 6 $var false false true true 7 $var=array() false true true false 8 $var=array(1) true true false false 以下、それぞれについて解説します。 PHPのisset関数 変数が宣言されていること、そして null とは異なることを検査する 公式ドキュメントより 「変数が宣言されていること」がempty, is_nullと違う点なので注意です。 表5のケースでは変数$varにnullがセットされているのでfalse。 表6のケースでは変数$varは宣言されているものの、値はセットされていないのでfalse。 それ以外のケースでは、空文字/数字の0/空の配列だろうと、nullでないものがセットされてるのでtrueです。 PHPのempty関数 変数が空であるかどうかを検査する 公式ドキュメントより PHPのempty関数は「空であるかどうか」を判定します。 空文字/数字の0/文字列の0/null/空の配列などがtrueになります。 PHPのis_null関数 変数が null かどうか調べる 公式ドキュメントより 名前の通り、null/何もセットされていない場合にtrueになります。 と、いうことはissetと!is_nullは一緒やんな?→違います ぱっと見、issetとis_nullは正反対の判定をしているように見えます。 ということはissetと!is_nullどっち使ってもいいはず...と思ったあなた、ダウトです。 issetは「変数が宣言されていること」も判定していますが、is_nullは関係ありません。 test.php $x = 'test'; var_dump(isset($y)); // 結果(変数$yは宣言されていないのでfalse) bool(false) $x = 'test'; var_dump(!is_null($y)); // 結果(そもそも$yが宣言されていないのでundefined) PHP Notice: Undefined variable: y in test.php on line 7 おわりに PHPのnull判定関数(isset, empty, is_null)の違いについてまとめました。 特にissetと!is_nullは素人目には同じように思えましたが、きちんと調べると挙動が違うことが理解できました。 参考記事 PHP isset, empty, is_null の違い早見表
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

word pressにてgoogle vision apiを使い画像の検索機能を作成する

word press(wp)環境下で、Google Cloud Vision Apiを使用して苦労した話 https://zenn.dev/crinoid/articles/f41a4a09753a71#11.-web_detection こういった記事を見つけ、画像を検索するシステムを作成しようと思った。 公式: wpにて、apiを使用する まず、apiの登録をする。こちらは結構情報があるので割愛。 downloadしたjsonを用意する。(image.json) vision clientを使う 結構情報が少なく苦労した部分ですね。特にPHPの情報が少ない.... 参考: https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/v0.165.0/vision/visionclient?method=annotate ※vision clientを今回使いましたが、image annotate clientを使うことをお勧めします。(次のリリースでvisionは非推奨になるため) ただ今回は、visionの情報しか見つけることができなかったため、vision clientを使います。 test.php use Google\Cloud\Vision\V1\ImageAnnotatorClient; use Google\Cloud\Vision\V1\Feature; use Google\Cloud\Vision\V1\Feature\Type; use Google\Cloud\Vision\VisionClient; use Google\Cloud\Storage\StorageClient; putenv('GOOGLE_APPLICATION_CREDENTIALS=/home/image.json'); require_once('/home/vendor/autoload.php'); $vision = new VisionClient(); $familyPhotoResource = fopen($_FILES['image']['tmp_name'], 'r'); $image = $vision->image($familyPhotoResource, [ 'web' ]); $result = $vision->annotate($image); $info = $result->info(); foreach( $info['webDetection']['pagesWithMatchingImages'] as $v ){ echo $v['url']; 参考: 上記のgithubが肝です。 donwloadしたimage.jsonを適切な場所に置き、pathを puenvを使い、GOOGLE_APPLICATION_CREDENTIALSに設定してあげます。→これにより、認証(auth)が通ります。 ※本来であれば、サーバーの環境変数に設定するのですが、wpだとこんな形になりました。(最善策より苦肉の策です) その後、vendorというフォルダをgithubからdownload! ※vendorを使う方法は2種類。composerを使うか、downloadして使うか。 しかし、require_once '/path/to/google-api-php-client/vendor/autoload.php';を使い、downloadを使ったのですがダメでした。 使用してみると、参照するファイルがないなどのエラーが頻発しまくりました。 そのため、composerを使うことを決めました。 composerを使ったとしても、composer require google/apiclient:^2.10を打ち込みフォルダをdownloadするだけです。 wpでcomposerが使えなかったとしても、downloadしたvendorのフォルダをまるまるコピーすれば使用可能になります。適当な場所にvendorフォルダを置いて、pathをrequire_onceに設定する とにかく、vision clientのインスタンスの作成に手こずりました。 classが見つかりませんなどのエラーが頻発しましたが、composerでdownloadしてきたvendorを使えば解決できました。 phpのデータの取得(オブジェクト) $familyPhotoResource = fopen($_FILES['image']['tmp_name'], 'r'); $image = $vision->image($familyPhotoResource, [ 'web' ]); $result = $vision->annotate($image); $info = $result->info(); 参考: ただの画像を検索するのであれば、 $familyPhotoResource = fopen('test.jpg', 'r'); にすれば良い。今回はformから送信した画像を検索にかけたかったため、このような形になりました。 web detectionという機能を用いて、似ている画像を検索するoptionを使います。 最後の$infoの情報は以下のようになっています。 Google\Cloud\Vision\Annotation Object ( [info:Google\Cloud\Vision\Annotation:private] => Array ( [webDetection] => Array ( [webEntities] => Array ( [0] => Array ( [entityId] => /g/11c4769n5h [score] => 1.282842 [description] => Carnival in Rio de Janeiro 2019 ) [fullMatchingImages] => Array ( [0] => Array ( [url] => https://triniinxisle.com/wp-content/uploads/2019/01/Carnival-Budget.jpg ) 取得できる情報は、phpのobjectで取得することができました。 取り方は、$info = $result->info();のように->info()を使ってobjectである [info:Google\Cloud\Vision\Annotation:private]の情報を取得する。 ここも苦労しました。(infoの記述がobjectだと判明するのに時間を要した..) info objectを取れたことにより、ネストされたwebEntitiesなどの情報が取れるようになりました。 for文などを用いて、中にある、urlやtitleの情報を取得することができ目的達成です! wp環境下での使用 wpにすでに上がっている、画像やurlがついている画像などは検索できるが formから送った画像方法を検索するのに骨が折れました。 自作のformを固定ページに作成し、別ページに飛ばす方法を取りました。 しかし、wpはformで情報を送ると、記事検索機能が働いてしまいます。 そのため、function.phpに新たに、関数を作成しそれを呼ぶように設定します。 short codeの自作を交えて、formから呼ぶようにすると、検索機能の出来上がりです。 function test(){ return test } add_shortcode('test1',test); //使用したい際には、[test1]を記述すればよい。 参考:https://www.webopixel.net/wordpress/53.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

wpにてdebugする

wp-config.php define( 'WP_DEBUG', true ); //ここをtrueにすることにより、デバッグ機能が動く if ( WP_DEBUG ) { define( 'WP_DEBUG_LOG', true ); define( 'WP_DEBUG_DISPLAY', false ); @ini_set( 'display_errors', 0 ); } //ここを記述することにより、debug.logにエラーなどのlogが表示される。 そのため、検証が可能になる。 参考:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】バージョンを確認するときのコマンド

はじめに 現在自分が使っているLaravelのバージョンを確認する方法を見ていきましょう。 Laravelのバージョン確認方法 ターミナル php artisan --version ターミナル php artisan -V Laravel Framework 5.8.38のようにバージョンが表示されればOKです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト指向プログラミング入門 for PHP programmers (3)〜継承編

この記事について この記事はオブジェクト指向プログラミング入門 for PHP programmers (2)〜オブジェクト編 - Qiitaの続編です。 前回「ひとつのアクション対してロジックが複数存在するケース」のところで続きを書きますと宣言していたので、書きました。 本記事では、基底クラス、抽象基底クラス、インターフェイス、トレイトあたりを扱います。 前回同様「実際の設計・実装」とあまり乖離してない例で解説してみようとする試みです。 はじめに 今回は以下のトピックを取り上げます。 継承 インターフェイス トレイト これらのトピックを説明しようとすると長くなってしまうので、本文中でその都度説明していこうと思います。 環境 PHP: 8.0.10 目次 継承 インターフェイス トレイト 合成と委譲 1. 継承 extends キーワードを用いて、クラス間でデータや関数を共有します。必要最低限の構文は以下です。例として、ユーザーというクラスがあり、それを拡張してプレミアムユーザー(課金しているユーザー)を定義してみます。 class User { } class PremiumUser extends User { } このままではなにも起きないので、インスタンスをつくって出力してみます。その際、基底クラスにプロパティを持たせてみます。 class User { public string $name; protected int $baseFee = 0; private bool $isEmailConfirmed = false; } class PremiumUser extends User { protected int $baseFee = 1000; } $user = new PremiumUser('John'); var_dump($user); /* 以下が出力される object(PremiumUser)#1 (3) { ["baseFee":protected]=> int(1000) ["name"]=> string(4) "John" ["isEmailConfirmed":"User":private]=> bool(false) } */ 継承先のクラス PremiumUser は自身でプロパティを持っていませんが、基底クラスで定義した3つのプロパティの値が出力されています。$baseFee を上書きしているので、親クラスで定義された 0 ではなく、1000 が出力されています。 各プロパティ定義の先頭にある public, protected, private はプロパティのアクセス権です。詳しくは公式リファレンスに載っているので、下記のページを読んでください。 PHP: アクセス権 - Manual プロパティに対するアクセス権は、データの読み書き両方です。どちらか片方のみ許可することはできないので注意してください。 1.1. 例)種別ごとの処理 継承が適用できる典型的なパターンは、「○○種別」とか「○○タイプ」みたいなプロパティがあり、それらの種別ごとに振る舞いが違うケースです。例として、アンケートなどで使用する質問を継承を使って表現してみます。 class Question { protected string $text; public function __construct(string $text) { $this->text = $text; } } class ChoiceQuestion extends Question { private array $options; public function __construct(string $text, array $options) { parent::__construct($text); $this->options = $options; } } Question は自由入力形式の質問、ChoiceQuestion は与えられた選択肢から選択するタイプの質問です。 これらのクラスに質問を出力する関数を追加してみますが、その前に、非オブジェクト指向的に書くとどうなるか比較のために書いておきます。 非オブジェクト指向バージョンだとこういう使い方になるでしょう。 function renderQuestion(array $question): string { if ($question['type'] === 'choice') { return sprintf('%s (%s)', $question['text'], implode(', ', $question['options'])); } return $question['text']; } $questions = [ [ 'text' => 'お名前は?', 'type' => 'text', ], [ 'text' => '好きなのはどちらですか?', 'type' => 'choice', 'options' => ['a', 'b'], ], ]; foreach ($questions as $question) { echo renderQuestion($question), PHP_EOL; } /* お名前は? 好きなのはどちらですか? (a, b) */ 質問内容をデータベースで管理していれば type みたいなフィールドがあるでしょうから、それで条件分岐させます。質問タイプが増えれば renderQuestion の中身が膨らむことが容易に想像できます。その対策として、質問タイプごとに関数をつくって分割することになります。 function renderGenericQuestion(string $text): string { return $text; } function renderChoiceQuestion(string $text, array $options): string { return sprintf('%s (%s)', $text, implode(', ', $options)); } function renderQuestion(array $question): string { if ($question['type'] === 'choice') { return renderChoiceQuestion($question['text'], $question['options']); } return renderGenericQuestion($question['text']); } では、オブジェクト指向バージョンではどうなるか、各クラスに render メソッドを追加してみます。 class Question { protected string $text; public function __construct(string $text) { $this->text = $text; } public function render(): string { return $this->text; } } class ChoiceQuestion extends Question { private array $options; public function __construct(string $text, array $options) { parent::__construct($text); $this->options = $options; } public function render(): string { return sprintf('%s (%s)', $this->text, implode(', ', $this->options)); } } 非オブジェクト指向バージョンと比較すると、以下の点が異なります。 処理対象が関数に含まれているか含まれていないか 非オブジェクト指向バージョンでは含まれている(動詞+名詞) オブジェクト指向バージョンでは含まれていない(動詞) 引数になにも渡さず、オブジェクトが持つプロパティを利用している 非オブジェクト指向バージョンでは関数にすべてを渡さなければならない オブジェクト指向バージョンではコンストラクタで受け取ったデータを利用できる // 非オブジェクト指向バージョン function renderChoiceQuestion(string $text, array $options): string { return sprintf('%s (%s)', $text, implode(', ', $options)); } // オブジェクト指向バージョン public function render(): string { return sprintf('%s (%s)', $this->text, implode(', ', $this->options)); } 使い方は以下のようになるでしょう。 $questionData = [ [ 'text' => 'お名前は?', 'type' => 'text', ], [ 'text' => '好きなのはどちらですか?', 'type' => 'choice', 'options' => ['a', 'b'], ], ]; class QuestionFactory { public function __invoke(array $properties): Question { if ($properties['type'] === 'choice') { return new ChoiceQuestion($properties['text'], $properties['options']); } return new Question($properties['text']); } } $questions = array_map(new QuestionFactory(), $questionData); foreach ($questions as $question) { echo $question->render(), PHP_EOL; } /* お名前は? 好きなのはどちらですか? (a, b) */ 質問タイプごとの条件分岐は QuestionFactory クラスに移動しました。 非オブジェクト指向バージョンでは、条件分岐が render() 関数内にあったのに対し、オブジェクト指向バージョンではオブジェクトの生成するクラス(ファクトリ)に移動したわけですが、これのメリットとしては、タイプごとに異なる処理が複数あった場合、条件分岐が一箇所で済むことです。一箇所で済めば、質問タイプが増えたときにその箇所に処理を追加すれば済むようになります。したがって、質問タイプに対する条件分岐が多くなればなるほどメリットは大きくなります。 また、別のクラスにインスタンスを引数として引き渡す際、基底クラスのインスタンスとして渡せば、質問タイプの違いを意識することなく処理を行えるので、無駄な条件分岐を書かずに済みます。 // 別のクラスに渡す public function doSomethingWithQuestion(Question $question): void { // この中では Question なのか ChoiceQuestion なのかを気にしなくて済むように、 // 質問タイプごとに異なる処理はすべて Question 側に押し込む } 「ひとつのアクション対してロジックが複数存在するケース」で、かつそれが複数組み合わさっている場合に継承が有用というのはあるていどわかってもらえたのではないかと思います。 1.2. 抽象基底クラス 上の例では基底クラスである Question はインスタンス化(new で生成)可能でした。継承の別のパターンとして抽象基底クラス( abstract class )にしてみます。 abstract class Question { protected string $text; public function __construct(string $text) { $this->text = $text; } // 親クラスで abstract なメソッドを定義すると子クラスで実装しないとエラーになる abstract public function render(): string; } class TextQuestion extends Question { public function render(): string { return $this->text; } } PHP: クラスの抽象化 - Manual アブストラクトな基底クラスにはアブストラクトなメソッドを用意するのが一般的です(上の例では render())。もちろん、アブストラクトなメソッドがひとつもなくてもクラスをアブストラクトにすることはできますが、メリットを削ぐことになると思います。アブストラクトなメソッドを定義するメリットは、継承する子クラスで実装することを強制できることです。子クラスでの実装漏れにより、意図せず基底クラスで実装されたデフォルトの動作をしてしまうことを防ぐことができます。 また、アブストラクトなクラスはインスタンス化することはできません。 $abstractQuestion = new Question(); // エラー $textQuestion = new TextQuestion(); // OK 基底クラスをアブストラクトにするかどうかの判断基準としては、 基底クラスで定義したメソッドにデフォルトの振る舞いを持たないようにしたい という点があります。今回のケースでは、どちらを採用しても大差ないかなと思いますが、大きな処理のうち一部だけを共通化したい、みたいなケースでは、アブストラクトを使ったほうが便利なケースが多いです。 // 親クラスでは処理フローだけを記述する abstract class Base { public function doSomething(): void { $this->prepare(); $this->process(); $this->cleanUp(); } // 実装は子クラスで各々定める abstract protected function process(): void protected function prepare(): void { // デフォルトの動作 } protected function cleanUp(): void { // デフォルトの動作 } } 2. インターフェイス インターフェイスは継承の一種として捉えられます。これまで見てきたクラスがクラスを継承する( extends キーワードを用いた継承)のと異なり、インターフェイスはメソッドのシグネチャ(名前、引数の数と型、戻り値の型)を引き継ぎます。 アブストラクトな基底クラスで、アブストラクトなメソッドを定義すれば、その実装を子クラスに対して強制することができる、というメリットがあることを示しました。似たような機能にインターフェイスがあります。抽象基底クラスとインターフェイスの違いは、抽象基底クラスがプロパティを持てるのに対し、インターフェイスではメソッド定義と定数しか持つことができません。 構文は以下のとおりです: interface Renderable { public function render(): string; } PHP: オブジェクト インターフェイス - Manual このままではインスタンス化はできず、実装( implements キーワードを使う)したクラスを別途定義しなければなりません。 class Question implements Renderable { // インターフェイスで定義されたメソッドとシグネチャを一致させなければならない public function render(): string { return $this->text; } } (上の例はあまりインターフェイスを使うメリットがありません。手抜きで最初の例を援用しました。その点ご容赦ください) 命名に関していくつか流派があり、 動詞 + able 例)Renderable 名詞 + Interface 例)RendererInterface I + 名詞 例)IRenderer クラスと区別せず 例)Renderer どれがいいとも一概に言えないですが、チームやプロダクトで統一しておけばいいのかな、と思います。個人的には、原則的に 1 と 4 を使い分けていて、公開メソッドがひとつであれば 動詞 + able、複数であればクラスと区別しない名前にしています。 継承とインターフェイスの違いとしては、継承がひとつの継承元しか持つことができないのに対し(C++のように多重継承が可能な言語もありますが、PHP では許可されていません)、インターフェイスはいくつも実装することができます。たとえば、Laravel の Model だと実に 7つものインターフェイスを実装しています。 abstract class Model implements Arrayable, ArrayAccess, HasBroadcastChannel, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable {} 2.1. 例)リトライ可能なコマンドをリトライする インターフェイスが有用である典型的なケース例を挙げるのはなかなか難しいんですが、要は「○○できる」ということだけはたしかだが、それをどう実施するかは決まってない(正確には、個々のクラスに任されている)、というシチュエーションで有用です。例として、アプリケーション上で自動実行されるコマンドが複数あり、それらが失敗した場合、失敗した状況に応じてリトライ可能であり、それを UI から一括でリトライできるとします(このような UI が適切かどうかはさておき)。ただし、すべてのコマンドがリトライ可能なわけではなく一部である、とします。 interface RetryableCommand { public function retry(): void; } リトライ実行時の振る舞いは各クラスに完全に委ねられていて、前に実行したコマンドをそのまま実行してもいいし、前処理や後処理を行ってから実行してもいいわけです。この例では、Laravel のポリモーフィック関連をつかってコマンドの実行履歴が保持されていて、そのレコードを元にリトライ可能なものだけを選別して一括リトライしています。 // リトライ不可 class CommandA {} // リトライ可 class CommandB implements RetryableCommand { public function retry(): void { echo 'Command B is retrying...', PHP_EOL; } } $commandLogs = [ [ 'executable_id' => 1, 'executable_type' => 'App\Commands\CommandA', 'result' => 'failed', ], [ 'executable_id' => 1, 'executable_type' => 'App\Commands\CommandB', 'result' => 'failed', ], ]; /** @var RetryableCommand[] $retryableCommands */ $retryableCommands = []; foreach ($commandLogs as $log) { if ($log['result'] !== 'failed') { continue; } $command = new $log['executable_type']()) if ($command instanceof RetryableCommand) { $retryableCommands[] = $command; } } foreach ($retryableCommands as $command) { $command->retry(); } 本来なら Model::morphTo() を使いますが、処理の簡略化のために new $log['executable_type']() しています フィルターの方法はいくつかあると思います。PHP には method_exists() という関数があるので、いちばんシンプルなパターンであれば、 foreach ($commandLogs as $log) { if ($log['result'] !== 'failed') { continue; } $command = new $log['executable_type'](); if (method_exists($command, 'retry')) { $command->retry(); } } というようにループ内で呼び出すことも可能です( foreach の対象である配列のすべての要素が RetryableCommand のインスタンスである保証はないので、このケースであればこちらのほうが安全です)。 PHP はあくまでも動的型付け言語なので、実行時に配列のすべての要素が RetryableCommand のインスタンスである保証はありませんし、メソッドが存在しなければ実行時エラー( Call to undefined method)になります。上記の例のように、外部から与えられるデータに基づいてインスタンスを生成するケースではとくに注意が必要です。 とはいえ、インターフェイスにするメリットはあって、PhpStorm などの IDE から検索が容易になるということです。仮に retry() というメソッドがこのコマンドの文脈以外にも存在していると、retry という名前でソースコード中を検索しなければならず、そうすると余計な行がヒットしてしまうので、できればインターフェイスを作成してほしいと個人的には思います(上の例では各コマンドクラスはおそらく同一の名前空間に存在しているはずなので、検索時のスコープを絞れば同程度の容易さで検索できるとは思いますが…) 3. トレイト これまで見てきた例では、親クラス(インターフェイスを含む)と子クラスの関係は、いわゆる IS-A の関係でした。オブジェクト指向設計やオブジェクト指向プログラミングの文脈でたびたび登場する Animal クラスと Dog や Cat クラスのような例でも、この IS-A 関係が成り立ちます。しかし、処理の共通化においてすべてのケースが IS-A 関係で表現できるわけではありません。そういう、同一視はできないが共通の処理を持たせたい、という場合によく使います。 構文は以下のとおりです: trait HasUuid { } PHP: トレイト - Manual トレイトにはプロパティとメソッドを持たせられます。 3.1. 例)UUID を持つモデル ちょうど最近トレイトを使った例の出てくる記事を書いたので、流用します。Laravel で uuid を持たせたい場合、トレイトにして各モデルクラスで use することで、インスタンス生成時に自動的に UUID を発行して保存させることができます。 Laravel で長いコールバック関数を関数化する方法 - Qiita trait HasUuid { public static function bootHasUuid(): void { static::creating(function ($model) { $model['uuid'] = Str::uuid(); }); } } class SomeModel extends Model { use HasFactory; use HasUuid; } 各モデルには「UUID を持つ」という共通点はありますが、けっして IS-A の関係ではありません。それでも共通の処理を持たせたいので、このケースではトレイトを使うのがいいと思います。 トレイトは名前の衝突が起こり得たり、使用するクラスのプロパティを参照する際は補完が効かなかったり、扱いが難しい仕組みなので、使用する際は注意してください。 個人的には、トレイト内にプロパティを定義しない、使用するクラスのプロパティを参照する場合はバリデーションをする( assert を使えばいいかな、と思います)、なるべくメソッド数を少なくする、といった点に気をつけています。 3.2. トレイトとインターフェイスを組み合わせる 余談ですが、トレイトとインターフェイスと組み合わせて使用するといいケースも少なからずあります。 インターフェイスはクラスの外に対して、このオブジェクトはこのメソッドを持っていますよ、というヒントを与えるだけです。なので、そのメソッドの中身がクラスにあろうとトレイトにあろうと関係ありません。 例として、Laravel を使ったアプリケーションで、相互に無関係な2つのモデル(テーブル)があり、それぞれに別々の監査ログテーブルがあるとします(監査ログテーブルを個別に持つのがいい設計かどうかは要件によりますが、ここでは個別に持つほうがいいものとします)。しかし、監査ログをひとまとめに扱いたいユースケースがあるため、それをインターフェイスとトレイトを使って表現してみます(動作未検証のため誤りがあるかもしれませんが、その点ご容赦ください。雰囲気だけ掴んでいただければ)。 interface Auditable { public function auditLogs(): HasMany; } trait HasAuditLogs { public function auditLogs(): HasMany { // プロパティおよびメソッドの存在チェックをしておいたほうがいいでしょう $method = $this->auditLogsFunction; return $this->$method(); } } class SomeModel extends Model implements Auditable { use HasAuditLogs; private string $auditLogsFunction = 'someLogs'; public function someLogs(): HasMany { return $this->hasMany(SomeLog::class); } } class AnotherModel extends Model implements Auditable { use HasAuditLogs; private string $auditLogsFunction = 'anotherLogs'; public function anotherLogs(): HasMany { return $this->hasMany(AnotherLog::class); } } // 呼び出し側 $someLogs = SomeModel::find(1)->auditLogs; $anotherLogs = AnotherModel::find(1)->auditLogs; // または /** @var array|Auditable[] */ $auditableModels = [ SomeModel::find(1), AnotherModel::find(1), ]; $auditLogs = []; foreach ($auditableModels as $model) { $auditLogs = array_merge($auditLogs, $model->auditLogs); } クラスの外側からはインターフェイスで定義された auditLogs() を参照し、内部的にはトレイトで定義された auditLogs() が呼び出される、という構成になっています。 4. 合成と委譲 さらに余談ですが、処理の共通化の方法として、継承はなるべく使わず、合成と委譲を使いましょう、という提言(?)もあります。 「継承より合成(Composition over inheritance)」と呼ばれます。 Composition over inheritance - Wikipedia 継承は、親子関係がひとつだけならまだしも、孫・曾孫みたいな深い階層になってくると、この変数はどのクラスでどんな処理が行われた結果この値になってるんだ、みたいなことがわかりずらくなってつらいので、共通処理を親にさせる(継承)のではなく、専用のクラスのインスタンスを内部で保持して(合成)そいつにさせよう(委譲)、ということです。 4.1. 例)カスタムコレクション PHP にはジェネリクスがないので、特定のクラスのインスタンスしか受け付けないことを強制するクラスがつくりたければ、自作するしかありません。下記のように Laravel の Collection を継承して、要素を追加するときに引数をチェックすることで縛ることができます(実行時エラーを出すのが最適かどうかはさておき)。 class UserCollection extends Collection { public function add($user) { if (!$user instanceof User) { throw new InvalidArgumentException('it must be a user.'); } return parent::add($user); } } しかし、上記の add() メソッドは親クラスである Collection にある add() メソッドのオーバーライドなので、受け付ける変数の型はあくまでも親クラスで定義された mixed で、これを変えることはできません(PHP では関数のオーバーロードはできません)。 オーバーライド: 親クラスで定義されたメソッドを子クラスで再定義する オーバーロード: 名前は同じだが引数の型が異なるメソッドを定義する これを合成と委譲で表現してみます。 class UserCollection { private Collection $items; public function __construct() { $this->items = collect(); } public function add(User $user): self { $this->items->add($user); return $this; } } 内部で Collection のインスタンスを持ち、要素の追加をするときには $this->items に処理を丸投げします。 どちらのやり方にもメリット・デメリットがあって、必ずしも「継承より合成」というわけではないと思いますが、個人的には、カスタムコレクションクラスを自作するのであればそれなりの理由(ドメインに固有のなにか特別な処理が必要、とか)があるはずで、であれば、ドメインに固有の処理だけを実装できる合成のほうが適しているかな、と思います(そういう意味では、自分なら、単に型を縛りたいというだけの理由でカスタムコレクションクラスはつくらないと思います)。 ちなみに、PHP では、マジックメソッド __call() を使えば、コンポーネント(合成される側のオブジェクト)へ自動的に処理を委譲させることができます。 PHP: オーバーロード - Manual 先ほどのカスタムコレクションの例では、以下のようになるでしょう。 class UserCollection { private Collection $items; public function __construct(array $items = []) { $this->items = collect($items); } // 指定されたメソッドが存在しなければ自動的に呼ばれる public function __call(string $name, array $params) { if (!method_exists($this->items, $name)) { throw new BadMethodCallException('method does not exist - ' . $name); } $this->items->$name(...$params); return $this; } } // 呼び出し側 $users = new UserCollection(); $users->add(new User()); 呼び出し側は add() を呼んでいますが、UserCollection クラスには add() メソッドは存在しないので、代わりに __call() が呼ばれ、 $this->items->add() を代わりに呼びます。 まぁ、マジックメソッドは無闇に使わないほうがいいとは思いますが、テストが十分に書いてあればそれほど怖がることもないかなとも思うので、ここぞというときにお使いいただければ(推奨はしませんが)。 おわりに 複数の重たいトピックをひとつの記事にまとめてしまったので長くなってしまいました。もし、ここの説明がよくわからん、みたいな箇所があれば、コメント欄にてご指摘ください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DeepL API の使用方法

auth_key と text, source_lang, target_lang を指定して GET or POST するだけ 参考: https://www.deepl.com/docs-api/introduction/ シェルスクリプト deepl.sh #!/bin/sh DEEPL_API_URL="https://api-free.deepl.com/v2/translate" YOUR_API_KEY="__DEEPL_API_KEY_HERE__" SOURCE_TEXT=" I'm a lumberjack and I'm OK. I sleep at night, I work during the day. He's a lumberjack and he's OK. He sleeps all night and he works all day. I cut down trees, I eat my lunch. I go to the lavatory. On Wednesdays I go shopping and have buttered scones for tea. " curl -s ${DEEPL_API_URL} \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "auth_key=${YOUR_API_KEY}" \ -d "text=${SOURCE_TEXT}" \ -d "source_lang=EN" \ -d "target_lang=JA" \ | jq .translations[].text Python deepl.py import requests DEEPL_API_URL = 'https://api-free.deepl.com/v2/translate' YOUR_API_KEY = '__DEEPL_API_KEY_HERE__' SOURCE_TEXT = """ I'm a lumberjack and I'm OK. I sleep at night, I work during the day. He's a lumberjack and he's OK. He sleeps all night and he works all day. I cut down trees, I eat my lunch. I go to the lavatory. On Wednesdays I go shopping and have buttered scones for tea. """ params = { "auth_key": YOUR_API_KEY, "text": SOURCE_TEXT, "source_lang": 'EN', "target_lang": 'JA' } request = requests.post(DEEPL_API_URL, data=params) result = request.json() print(result["translations"][0]["text"]) Node.js deepl.js const request = require('request'); const deepl_api_url = 'https://api-free.deepl.com/v2/translate' const your_api_key = '__DEEPL_API_KEY_HERE__'; const source_text = ` I'm a lumberjack and I'm OK. I sleep at night, I work during the day. He's a lumberjack and he's OK. He sleeps all night and he works all day. I cut down trees, I eat my lunch. I go to the lavatory. On Wednesdays I go shopping and have buttered scones for tea. `; const params = { url: deepl_api_url, method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, form: { auth_key: your_api_key, text: source_text, source_lang: 'EN', target_lang: 'JA' }, json: true } request.post(params, function(error, response, result){ console.log(result.translations[0].text); }); PHP deepl.php <?php $deepl_api_url = 'https://api-free.deepl.com/v2/translate'; $your_api_key = '__DEEPL_API_KEY_HERE__'; $source_text = " I'm a lumberjack and I'm OK. I sleep at night, I work during the day. He's a lumberjack and he's OK. He sleeps all night and he works all day. I cut down trees, I eat my lunch. I go to the lavatory. On Wednesdays I go shopping and have buttered scones for tea. "; $header = [ 'Content-Type: application/x-www-form-urlencoded', ]; $content = [ 'auth_key' => $your_api_key, 'text' => $source_text, 'source_lang' => 'EN', 'target_lang' => 'JA', ]; $params = [ 'http' => [ 'method' => 'POST', 'header' => implode("\r\n", $header), 'content' => http_build_query($content, '', '&'), ] ]; $request = file_get_contents( $deepl_api_url, false, stream_context_create($params) ); $result = json_decode($request); echo $result->translations[0]->text; 実行結果 私は木こりですが、大丈夫ですよ。 夜は寝て、昼は働く。 彼は木こりであり、彼はOKだ。 彼は夜も寝るし、昼も働く。 木を切って、お昼を食べて。 お手洗いにも行く。 水曜日には買い物に行き、紅茶にバター入りのスコーンを食べる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】LaravelでYoutubeのお気に入りCuration(まとめ)アプリを作る【第4回: ログイン&動画登録】

(この記事は作成中です。) こんにちは。 ITエンジニアの濱辺(ハマベ)です。 今回は、ログイン&お気に入り動画登録機能を実装していきます。 ↓第3回はこちら LaravelでYoutubeのお気に入りCuration(まとめ)アプリを作る【第3回】 ↓こちらの画面定義書のものを作っていきます。 Youtube-Curation 画面定義所 (googleスプレッドシート) ログイン機能の実装 ログイン機能についても、Laravelに標準で備わっているファイルがあります。 基本機能は最初から用意されているんですね。 Route設定 下記3つのrouteを記述しましょう。 web.php Route::get('login', 'Auth\LoginController@showLoginForm')->name('login'); Route::post('login', 'Auth\LoginController@login')->name('login.post'); Route::get('logout', 'Auth\LoginController@logout')->name('logout'); それぞれ、 - ログインページ表示 - ログインフォーム情報の送信 - ログアウト処理 のためのrouteです。 LoginController確認 Controllers/Auth/LoginControllerを開いてみましょう。 LoginController.php <?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Auth\AuthenticatesUsers; class LoginController extends Controller { //(中略) use AuthenticatesUsers; /** * Where to redirect users after login. * * @var string */ protected $redirectTo = RouteServiceProvider::HOME; /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest')->except('logout'); } } ログアウト以外のアクションは、guest(非ログイン)ユーザしか実行できない、といったことが記述されています。 上記記述のうち、推移先のページ情報だけ書き換えましょう。 LoginController.php protected $redirectTo = RouteServiceProvider::HOME; ↓ //変更 protected $redirectTo = '/'; ログインページの見た目実装 ログインページのviewを作成します。 view/auth/login.blade.phpを作成しましょう。 login.blade.php @extends('layouts.app') @section('content') <div class="center jumbotron bg-warning"> <div class="text-center text-white"> <h1>YouTubeまとめ × SNS</h1> </div> </div> <div class="text-center"> <h3 class="login_title text-left d-inline-block mt-5">ログイン</h3> </div> <div class="row mt-5 mb-5"> <div class="col-sm-6 offset-sm-3"> {!! Form::open(['route' => 'login.post']) !!} <div class="form-group"> {!! Form::label('email', 'メールアドレス') !!} {!! Form::email('email', old('email'), ['class' => 'form-control']) !!} </div> <div class="form-group"> {!! Form::label('password', 'パスワード') !!} {!! Form::password('password', ['class' => 'form-control']) !!} </div> {!! Form::submit('ログイン', ['class' => 'btn btn btn-primary mt-2']) !!} {!! Form::close() !!} <p class="mt-3">{!! link_to_route('signup', '新規ユーザ登録する?') !!}</p> </div> </div> @endsection これでログインができるようになりました。 ヘッダーからログインページへ推移できるようにする ヘッダーを書き換えて、ログイン状態と非ログイン状態で表示分けできるようにしましょう。 header.blade.php <header class="mb-5"> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="/">YouTube-Curation</a> <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#nav-bar"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="nav-bar"> <ul class="navbar-nav mr-auto"></ul> <ul class="navbar-nav"> @if (Auth::check()) <li class="nav-item">{!! link_to_route('logout', 'ログアウト', [], ['class' => 'nav-link']) !!}</li> <li class="nav-item"><a href="" class="nav-link">マイページ</a></li> @else <li class="nav-item">{!! link_to_route('signup', '新規ユーザ登録', [], ['class' => 'nav-link']) !!}</li> <li class="nav-item">{!! link_to_route('login', 'ログイン', [], ['class' => 'nav-link']) !!}</li> @endif </ul> </div> </nav> </header> @if (Auth::check())から、@elseまでの間がログイン状態での表示。 @elseから@endifの間が非ログイン状態の表示を表しています。 Auth::check()は「ユーザがログイン状態か?」を判断する関数です。 トップページにログインユーザ名を表示 トップページにログインしたユーザの名前を表示させましょう。 welcome.blade.php(一部抜粋) @extends('layouts.app') @section('content') <div class="center jumbotron bg-warning"> <div class="text-center text-white"> <h1>YouTubeまとめ × SNS</h1> </div> </div> <div class="text-right"> @if(Auth::check()) {{ Auth::user()->name }} @endif </div> @endsection さて、一通り実装できたので、ログイン・ログアウトを実行してみましょう。 ログイン画面 ログインした状態 ログアウト実行後(非ログイン状態) 無事に動作したでしょうか。 できましたら、動画登録機能の実装をしていきましょう! 動画登録の実装 まずは、movie用のmysqlテーブルを作成していきます。 Moviesテーブルを作成 まず、Moviesテーブルを作成します。 下記のコマンドを実行しましょう。 # php artisan make:migration create_movies_table --create=movies ファイルが作成されるので、中身up()の中身を書き換えましょう。 [作成日時]_create_movies_table.php(一部抜粋) public function up() { Schema::create('movies', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned()->index(); $table->string('url'); $table->string('comment')->nullable(); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); } 5つのカラムを作るように記述しました。 id:各動画に付ける連番 user_id:動画を登録したユーザのID url: YouTube動画のURL comment: 動画に対するコメント (nullable = nullでも投稿できる) timestamps:動画登録日時・動画更新日時 $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');は、紐づいているユーザIDを持つユーザが消去されたら、moviesテーブルの該当カラムも一緒に削除する、といった意味になります。 マイグレートを実行してみましょう。 # php artisan migrate すると、下記のエラーが発生するかと思います。 PDOException::("SQLSTATE[HY000]: General error: 3780 Referencing column 'user_id' and referenced column 'id' in foreign key constraint 'movies_user_id_foreign' are incompatible.") これは、usersテーブルのidと、moviesテーブルのidの型が異なることが原因で発生しています。 下記の部分を修正しましょう。 2014_10_12_000000_create_users_table.php Schema::create('users', function (Blueprint $table) { $table->increments('id'); 〜略 }); moviesモデルの作成 以下のコマンドにて、Movie モデルを作成しましょう。 # php artisan make:model Movie 生成されたMovie.phpに以下のように記述し、(’user_id’,’url’,’comment’)を登録できるようにしましょう。 Movie.php <?php namespace App; use Illuminate\Database\Eloquent\Model; class Movie extends Model { protected $fillable = ['user_id','url','comment'];    //多対一の関係 public function user() { return $this->belongsTo(User::class); } } Userモデルに、Movieモデルとの関係を記述しましょう。(一対多、一人のuserに複数のmovieの関係) User.php ~ 省略 ~ /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ];    //追加(movieとの一対多の関係) public function movies() { return $this->hasMany(Movie::class); } } ここまで出来たら、一度tinkerを使って動画の登録を試してみましょう。 >>> use App\User >>> $user = User::find(1) => App\User {#4104 id: 1, name: "sample1", email: "sample1@sample.com", email_verified_at: null, #password: "$2y$10$vHoB.o8.Gd.KGmkflb06MOVuRTnEU9KihfJ9himiOxjNCDaZ7d4oS", #remember_token: null, created_at: null, updated_at: null, }>>> use App\Movie >>> $user->movies()->get(); => Illuminate\Database\Eloquent\Collection {#xxx all: [], } >>> $user->movies()->create([ ... 'url' => 'Gn61Vq9v6GY', ... 'comment' => 'sample movie 1']) => App\Movie {#3324 url: "Gn61Vq9v6GY", comment: "sample movie 1", user_id: 1, updated_at: "2021-10-12 07:51:46", created_at: "2021-10-12 07:51:46", id: 1, } >>> $user->movies => Illuminate\Database\Eloquent\Collection {#4252 all: [ App\Movie {#4249 id: 1, user_id: 1, url: "Gn61Vq9v6GY", comment: "sample movie 1", created_at: "2021-10-12 07:51:46", updated_at: "2021-10-12 07:51:46", }, ], } MovieのRouter作成 Movie関連のRouterを記述していきます。 今回はresorceを使用します。resourceは、主要7つのrouteを自動作成してくれます。 web.phpを下記のようにしましょう。 web.php Route::get('/', 'UsersController@index'); //書き換え Route::get('signup', 'Auth\RegisterController@showRegistrationForm')->name('signup'); Route::post('signup', 'Auth\RegisterController@register')->name('signup.post'); Route::get('login', 'Auth\LoginController@showLoginForm')->name('login'); Route::post('login', 'Auth\LoginController@login')->name('login.post'); Route::get('logout', 'Auth\LoginController@logout')->name('logout'); // 追記分 Route::resource('users', 'UsersController', ['only' => ['show']]); //ログイン認証を通ったユーザのみ、アクセスできるroute Route::group(['middleware' => 'auth'], function () { Route::resource('movies', 'MoviesController', ['only' => ['create', 'store', 'destroy']]); }); UsersController作成 UserControllerを作成し、トップページを表示するindexアクションを記述していきます。 # php artisan make:controller UsersController app/Http/Controllers/UsersController.phpが作成される UsersController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\User; //追記 class UsersController extends Controller { public function index() { $users = User::orderBy('id','desc')->paginate(9); return view('welcome', [ 'users' => $users, ]); } } 'users' => $users,は、usersという変数をviewに持っていく、という処理を表しています。 Viewの作成 Movies の一覧を表示する共通の View として、 users.blade.php を作成します。 usersフォルダを作成し、ファイルを作っていきましょう。 resources/views/users/users.blade.php users.blade.php <h2 class="mt-5 mb-5">users</h2> <div class="movies row mt-5 text-center"> @foreach ($users as $key => $user) @php $movie=$user->movies->last(); @endphp @if($loop->iteration % 3 == 1 && $loop->iteration != 1) </div> <div class="row text-center mt-3"> @endif <div class="col-lg-4 mb-5"> <div class="movie text-left d-inline-block"> @{{ $user->name }} <div> @if($movie) <iframe width="290" height="163.125" src="{{ 'https://www.youtube.com/embed/'.$movie->url }}?controls=1&loop=1&playlist={{ $movie->url }}" frameborder="0"></iframe> @else <iframe width="290" height="163.125" src="https://www.youtube.com/embed/" frameborder="0"></iframe> @endif </div> <p> @if(isset($movie->comment)) {{ $movie->comment }} @endif </p> </div> </div> @endforeach </div> {{ $users->links('pagination::bootstrap-4') }} ここで少し、見た目を整えるためにCSSを適用しましょう。 下記のようにlinkを、app.blade.phpに追記します。 app.blade.php <head> <meta charset="utf-8"> <title>YouTubeまとめ×SNS</title> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">      //追加 <link rel="stylesheet" href="{{ asset('/css/styles.css') }}"> </head> 続いて、public/css/styles.cssを作成し、下記を記述します。 styles.css .movie{ width: 290px; } .movie > p{ height: 72px; } .button{ width: 290px; } これで、下記のような見た目となるはずです。 それぞれ、ユーザに登録した最新の動画のサムネが表示され、クリックすると、再生できるはずです。 ここまでで、「動画を保存できる」ようにはなりました。 次はwebページから動画を登録できるようにしていきます。 MoviesController作成 まずは、下記のコマンドでMoviesControllerを作成しましょう。 # php artisan make:controller MoviesController app/Http/Controllers/MoviesController.phpが作成されるので、 createアクションを記述していきましょう。 createアクション作成 MoviesController.php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\User; use App\Movie; class MoviesController extends Controller { public function create() { $user = \Auth::user(); $movies = $user->movies()->orderBy('id', 'desc')->paginate(9); $data=[ 'user' => $user, 'movies' => $movies, ]; return view('movies.create', $data); } } 今回は、$dataという変数に、$userと$moviesを配列として渡して、$dataをViewに渡すようにしています。 Viewの作成 まず、headerから動画の新規登録ページへ飛べるようにしましょう。 header.blade.php @if (Auth::check()) <li class="nav-item">{!! link_to_route('logout', 'ログアウト', [], ['class' => 'nav-link']) !!}</li> <li class="nav-item"><a href="" class="nav-link">マイページ</a></li> <li class="nav-item">{!! link_to_route('movies.create','動画を登録する',['id'=>Auth::id()],['class'=>'nav-link']) !!}</li> <!--↑追記--> @else 次に、動画登録フォームを作成しましょう。 resources/views/movies/create.blade.phpを新規作成します。 create.blade.php @extends('layouts.app') @section('content') <div class="text-right"> {{ Auth::user()->name }} </div> <h2 class="mt-5">動画を登録する</h2> {!! Form::open(['route'=>'movies.store']) !!} <div class="form-group mt-5"> {!! Form::label('url','新規登録YouTube動画 "ID" を入力する',['class'=>'text-success']) !!} <br>例)登録したいYouTube動画のURLが <span>https://www.youtube.com/watch?v=-bNMq1Nxn5o なら</span> <div>  "v="の直後にある "<span class="text-success">-bNMq1Nxn5o</span>" を入力</div> {!! Form::text('url',null,['class'=>'form-control']) !!} {!! Form::label('comment','登録動画へのコメント',['class'=> 'mt-3']) !!} {!! Form::text('comment',null,['class'=>'form-control']) !!} {!! Form::submit('新規登録する?',['class'=> 'button btn btn-primary mt-5 mb-5']) !!} </div> {!! Form::close() !!} <h2 class="mt-5">あなたの登録済み動画</h2> @include('movies.movies', ['movies' => $movies]) @endsection 下記の部分に注目してください。 <h2 class="mt-5">あなたの登録済み動画</h2> @include('movies.movies', ['movies' => $movies]) これは、moviesフォルダのmovies.blade.phpを読み込む記述です。 こうして別ファイルに分けた方が、後々の変更に強くなるのです。 というわけで、movies/movies.blade.phpを作成しましょう。 movies.blade.php <div class="movies row mt-5 text-center"> @foreach ($movies as $key => $movie) @if($loop->iteration % 3 == 1 && $loop->iteration != 1) </div> <div class="row text-center mt-3"> @endif <div class="col-lg-4 mb-5"> <div class="movie text-left d-inline-block"> <div> @if($movie) <iframe width="290" height="163.125" src="{{ 'https://www.youtube.com/embed/'.$movie->url }}?controls=1&loop=1&playlist={{ $movie->url }}" frameborder="0"></iframe> @else <iframe width="290" height="163.125" src="https://www.youtube.com/embed/" frameborder="0"></iframe> @endif </div> <p> @if(isset($movie->comment)) {{ $movie->comment }} @endif </p> </div> </div> @endforeach </div> {{ $movies->links('pagination::bootstrap-4') }} これで、ヘッダーの「動画を登録する」から遷移した画面がこうなるはずです。 MoviesContoroller storeアクション作成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【第九回】コロナ禍だから何かできることをー 自宅療養者連絡ツール ー

前回までのあらすじ 【第八回】コロナ禍だから何かできることを 実装イメージ図 LINEを利用して保健所の担当者の負担を軽減するとともに、自宅療養者はいつでもつながっている安心感を持たせるためのツールイメージです。 今回が最後なので、まとめていきたいます。 イメージ動画 まずは、第1回でも公開したイメージ動画です。 このアプリで期待できる効果 ・コロナ感染した自宅療養者の不安や寂しさを解消 LINEで担当者(保健所職員)とつながっていることや、 自動でシステムに管理してもらっていることから、 今までより格段に速く異常を察知できます。 そのため、一人という不安から解消されるはずです。 ・担当者(保健所職員)の負担軽減 第5波の時、自宅療養者が格段に増えたとき、担当者は毎日確認の連絡や訪問を行っていました。 それにより、1人辺りの担当人数がキャパシティをオーバーし、長時間労働をする形になりました。 今回、初めて知ったのですが、緊急時の場合は36協定等が適用外になるそうです。 つまり、緊急事態が解除されるまで働き続けることもありうるそうです。 そのため、このシステムで管理することにより、本当に連絡が必要な人を判断するこができ、 無症状の人を含めた全員に連絡をする必要がなくなるのです。 このことで、病院の手配や他の必要な業務にまわることができます。 システム構成まとめ 構成自体はシンプルです。 地区ごとに作業者と利用者を登録し、利用者の状態を履歴で保存し、異常を検知したら連絡をする。 いまさらながら、システムの流れ 地区と作業者は登録されているものとします。 1.陽性が判明すると、病院などから保健所へ連絡がきます(ここは憶測) 2.連絡を受けた保健所の担当者はこのシステムに利用者の登録をします。 3.保健所の担当者は陽性者へ連絡をします。   この時に、2で登録したユーザーコードを渡します。   ※メールでも、QRでもなんでもOK(ここは未実装) 4.陽性者はLINEのチャンネル登録後に伝えられたコードを入力します(ここで連携済) 5.陽性者は自宅待機中は常に症状の報告をします。 6.保健所の担当者は陽性者の登録パターンによって変えます。    ・LINEの連携がない場合は今まで通り    ・LINEの連携がある場合は異常になるか状態が変化するまで待機 7.保健所の担当者は陽性者の状態が変化したら状態を更新する。    ・未連絡、自宅待機、入院、その他    未連絡・自宅待機は管理対象    入院・その他は管理対象外 8,今回は、管理対象の陽性者から12時間以上連絡がない場合は注意喚起、   24時間以上連絡がない場合は警告を画面上に表示するとともにメールを送信   ※LINE連携がない陽性者は保健所の担当者が電話連絡や訪問を行い、画面上から登録します。 さいごに ニュースとかみてて、孤独死や保健所の担当者が大変な場面を見ました。 この時、自分には何ができるのかと思ったのですが、システム開発しかできません。 なので、ITの力で解決できるためにはを考えていたらこの仕組みを思いつきました。 この仕組み自体、だれでも思いつくし、だれでも実装できるのでもっと世の中に広まればいいのにって思いで公開に至りました。 第6波が来るのか来ないのかはわからないですが、仮に来たら第5波以上になることは確実です。 医療崩壊もそうですが、役所崩壊にならないことを祈るばかりです。 コロナ禍だから何かできることをー 自宅療養者連絡ツール ー 【第一回】実装イメージ図と動画 【第二回】LINEからデータを取得して返すまでの流れ 【第三回】LINEからデータベースまでの流れ 【第四回】データベースへの更新までの流れ 【第五回】ユーザー登録の仕組み-LINEbotの設定部分 【第六回】ユーザー登録の仕組みLIFFで表示する画面の開発 【第七回】データベースの構造 【第八回】WEB画面上でできる機能 ->>【第九回】まとめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

php-webdriver(selenium webdriver)でWeb-DevToolsのBlockedURLsを設定する方法

DXを実践するためにphp-webdriver selenium webdriverでクローリング等を行っているのですが どうもページの読み込みに詰まることがあって、応答がなくタイムアウトしてしまうことが多々あり 調べてみると画像ファイル取得でどうもなかなか応答がないっぽい。 こちら側の問題なのか先方のWebサーバの問題かわからず途方に暮れてた時、 別に画面スクショ取るわけではないので 画像取得は無視できたらいろいろ捗るなぁ・・と思いついて調べた次第。 HeadlessはChromeをつかっているけど、さくっとググっても見つからず たどり着いたのは公式のwiki。 https://github.com/php-webdriver/php-webdriver/wiki/Chrome まあ灯台もとぐらしってやつですな。 (github wikiってgoogle検索では引っかからない。あのUIでは多分クローラーが辿れないよね) use Facebook\WebDriver\Chrome\ChromeDevToolsDriver; と new ChromeDevToolsDriver() と execute('Network.setBlockedURLs',["urls"=> [array]) で実現できた。 (個人的にはレベルクズなエンジニアなので、何をuseすればいいのかがいつもわからない・・) RemoteWebDriverで対応してるのでlocalではこの方法ではないっぽいけど てめーのしたいことはこれで十分なので あと use Facebook\WebDriver\Chrome\ChromeDevToolsDriver; をするとExceptionが拡張されちゃう?っぽいので、 それぞれExceptionを拾うか catch(\Exception $e) と\をつけておくと良いっぽいです。 <?php namespace Facebook\WebDriver\Chrome; require_once('vendor/autoload.php'); use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use PHPUnit\Framework\TestCase; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\Chrome\ChromeDevToolsDriver; // selenium $seleniumhost = 'http://localhost:4444/wd/hub'; // chrome ドライバーの起動 $driver = RemoteWebDriver::create($host,DesiredCapabilities::chrome()); // ChromeDevToolsDriverの設定 $devTools = new ChromeDevToolsDriver($driver); //アクセスをブロックしたいURL $devTools->execute('Network.enable'); $devTools->execute('Network.setBlockedURLs', ["urls"=> [ "http://hogehoge/*/img/*.png", "http://hogehoge/img/*/*/*.gif", ] ] ); $oktitle="タイトル"; try{ $driver->get($url); $driver->wait(1)->until( WebDriverExpectedCondition::titleIs($oktitle) ); $html = $driver->getPageSource(); } catch (\Exception $e) { echo "Exception: " . $e->getMessage() . PHP_EOL; } ちなみに 業務システムやサイト作成などお仕事お待ちしております。 こんなの手伝ってほしいとか軽いお話からでもOKです! サーバ構築、DB設計、パフォーマンスチューニングなどもご希望に添えるように精一杯がんばります! お問い合わせはコメント欄にお願いします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む