- 投稿日:2020-08-06T21:27:38+09:00
【PHP】標準エラー出力する4つの方法
PHP で標準エラー出力にメッセージを出力する方法(各種)
Qiita 記事に絞って「php 標準エラー出力」でググっても、ピンポイントのタイトルの記事がなかったので、自分のググラビリティとして。
TL; DR
シンプルで確実な標準エラー出力fputs(STDERR, 'my error message');標準出力を潰して標準エラー出力だけ確認してみる$ # 標準出力へ出力(1>/dev/nullで標準出力を潰している) $ php -r 'fputs(STDOUT, "hoge". PHP_EOL);' 1>/dev/null $ $ # 標準エラー出力へ出力 $ php -r 'fputs(STDERR, "hoge". PHP_EOL);' 1>/dev/null hoge $ # STDERR定数の内容確認 $ php -r 'echo STDERR . PHP_EOL;' Resource id #3TS; DR
I/O ストリーム編
fwrite + STDERR定数
標準エラー出力への「出力ストリーム・リソース割り当て済み定数」である「
STDERR
」定数を使う。fclose()
の心配はいらない。入出力ストリーム定数を使った方法$msg_error = 'standard error' . PHP_EOL; fwrite(STDERR, $msg_error);
- 参考文献
- 入出力ストリーム定数 | コマンドラインの使用法 @ PHP 公式マニュアル
- fwrite | ファイルシステム関数 @ PHP 公式マニュアル
fputs + STDERR定数
fwrite
を使うとfclose
も使わないといけない印象を与えてしまうため、fputs
のエイリアスが用意されている。可読性を優先するならこちら。fwriteのエイリアス関数fputsを使ってもOK$msg_error = 'standard error' . PHP_EOL; fputs(STDERR, $msg_error);
- 参考文献: fputs | ファイルシステム関数 @ PHP 公式マニュアル
エラー処理関数編
error_log
関数の出力先を、標準エラー出力のファイル・ポインタ(php://stderr
)に設定する。後日、ファイル出力が必要な場合にも対応できるメリットがある。バイナリセーフではありません。PHPの設定ディレクティブ"error_log"に標準エラー出力へのファイルポインタを割り当ててini_set('error_log', 'php://stderr'); $msg_error = 'standard error' . PHP_EOL; error_log($msg_error);$ # 設定ディレクティブの確認(未設定を確認) $ php -r 'echo ini_get("error_log") . PHP_EOL;' $ php -r 'error_log("hoge");' 1>/dev/null hoge $ # 設定を変更してログ出力 $ php -r 'ini_set("error_log", "php://stderr"); echo ini_get("error_log") . PHP_EOL;' php://stderr $ php -r 'error_log("hoge");' 1>/dev/null hoge
- 参考文献: error_log | エラー処理関数 @ PHP 公式マニュアル
ファイル・ストリーム編
php://
ファイルストーリム「site:php.net/manual/ja 標準エラー出力」と PHP 公式マニュアルに絞ってググると出てくる方法。古いブロクでも良く見かける方法。
$msg_error= 'standard error' . PHP_EOL; $stderr = fopen('php://stderr', 'w'); fwrite($stderr, $msg_error); fclose($stderr);「ファイルを開く」「書き込む」「クローズ」という王道であるものの、いささか冗長に見えてしまう。また「ファイルのクローズ」といった無駄な心配事が増える気もするため、可読性が良いとは言えない。
- 投稿日:2020-08-06T21:07:17+09:00
タイムゾーンはちゃんと設定しよう
サービスを開発する際、最初にタイムゾーンをしっかり設定していないと、MySQLのcreated_at や updated_at などをうまく使えないというようなことになります。
MySQLでは、カラムにcreated_at や updated_atなどを加えると、それぞれSQLのinsertやupdateが実行されたときに自動でtimestampを入れてくれたりします。
始めにしっかり設定しておかないと、あとから必要になったときに、自力でそれらの値を取得して挿入するはめになってしまいます。(実体験)
- 投稿日:2020-08-06T18:54:44+09:00
【PHP入門】CRUD機能
はじめに
今回は、PHPとMySQLを使ってMVCモデルでCRUD機能を実装していきます。
CRUD機能とは?
ほぼ全てのソフトウェアが有する4つの永続的な基本機能の頭文字をそれぞれ並べた用語のこと。
その4つの機能とは、Create(生成)、Read(読み取り)、Update(更新)、Delete(削除)を指す。バージョン
PHP:7.4.5
phpMyAdmin:5.0.2
MySQL:5.7.30今回作成するファイル
conf / html / model / view の4つのディレクトリを作って進めます。
conf(繰り返し使用するもの定義)
- const.phphtml(controller)
- bbs.php
- bbs_insert.php
- bbs_update.php
- bbs_delete.phpmodel
- db.php
- functions.php
- bbs.phpview
- bbs_view.phpテーブルの作成
bbs.sqlCREATE TABLE `bbs` ( `id` int(11) NOT NULL, `name` varchar(100) NOT NULL, `comment` varchar(100) NOT NULL, `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ALTER TABLE `bbs` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, ADD PRIMARY KEY (`id`);定義
const.php<?php // ディレクトリパス define('MODEL_PATH', $_SERVER['DOCUMENT_ROOT'] . '/../model/'); define('VIEW_PATH', $_SERVER['DOCUMENT_ROOT'] . '/../view/'); // MySQL接続用 define('DB_HOST', 'ホスト名'); define('DB_NAME', 'データベース名'); define('DB_USER', 'ユーザ名'); define('DB_PASS', 'パスワード'); define('DB_CHARSET', 'utf8'); // URL define('BBS_URL', '/bbs.php');Viewの作成
bbs_view.php<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ひとこと掲示板</title> </head> <body> <h1>ひとこと掲示板</h1> <!-- 投稿フォーム --> <form method="post"> <div> <label>名前</label> <input type="text" name="name"> </div> <div> <label>コメント</label> <input type="text" name="comment"> </div> <input type="submit" value="送信"> </form> <!-- 表示 --> <table> <thead> <tr> <th>名前</th> <th>コメント</th> <th>投稿時間</th> <th>操作</th> </tr> </thead> <tbody> <tr> <td></td> <td></td> <td></td> <td></td> </tr> </tbody> </table> </body> </html>Create機能の実装
Modelの作成
modelの中にdb.phpを作成(データベース関係)
db.php<?php function get_db_connect(){ // MySQL用のDSN文字列 $dsn = 'mysql:dbname='. DB_NAME. ';host='. DB_HOST. ';charset='. DB_CHARSET; try{ // DB接続 $dbh = new PDO($dsn, DB_USER, DB_PASS, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4')); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); }catch(PDOException $e){ exit('接続できませんでした。理由:'.$e->getMessage()); } return $dbh; } function execute_query($db, $sql, $params = array()){ try{ $statement = $db->prepare($sql); return $statement->execute($params); }catch(PDOException $e){ set_error('更新に失敗しました。'); } return false; }modelの中にfunctions.phpを作成(その他機能)
functions.php<?php function redirect_to($url){ header('Location:'. $url); exit; } function get_post($name){ if(isset($_POST[$name]) === true){ return $_POST[$name]; }; return ''; } // セッション function get_session($name){ if(isset($_SESSION[$name]) === true){ return $_SESSION[$name]; }; return ''; } function set_session($name, $value){ $_SESSION[$name] = $value; } // エラーメッセージ function set_error($error){ $_SESSION['__errors'][] = $error; } function get_errors(){ $errors = get_session('__errors'); if($errors === ''){ return array(); } set_session('__errors', array()); return $errors; } // メッセージ function set_message($message){ $_SESSION['__message'][] = $message; } function get_messages(){ $messages = get_session('__message'); if($messages === ''){ return array(); } set_session('__message', array()); return $messages; }modelの中にbbs.phpを作成(掲示板関係)
bbs.php<?php require_once MODEL_PATH. 'functions.php'; require_once MODEL_PATH. 'db.php'; // Create function post_comment($db, $name, $comment){ $sql = " INSERT INTO bbs( name, comment ) VALUES(?,?) "; return execute_query($db, $sql, array($name, $comment)); }Viewの作成
bbs_view.phpにCreate処理へ遷移するよう追記
- 処理結果を表示できるよう追記
- formタグにactionを追記bbs_view.php<!-- 処理結果のメッセージ --> <?php foreach(get_errors() as $error){ ?> <p><?php print $error; ?></p> <?php } ?> <?php foreach(get_messages() as $messages){ ?> <p><?php print $messages; ?></p> <?php } ?> <form method="post" action="bbs_insert.php"> <div> <label>名前</label> <input type="text" name="name"> </div> <div> <label>コメント</label> <input type="text" name="comment"> </div> <input type="submit" value="送信"> </form>Controllerの作成
htmlの中にbbs.phpを作成(bbs_view.php表示用)
bbs.php<?php require_once '../conf/const.php'; require_once MODEL_PATH. 'bbs.php'; session_start(); include_once VIEW_PATH. 'bbs_view.php';htmlの中にbbs_insert.phpを作成(Insert処理用)
bbs_insert.php<?php require_once '../conf/const.php'; require_once MODEL_PATH. 'functions.php'; require_once MODEL_PATH. 'bbs.php'; session_start(); // データベース接続 $db = get_db_connect(); // Postされたものを定義 $name = get_post('name'); $comment = get_post('comment'); // Insert if(post_comment($db, $name, $comment)){ set_message('コメントを投稿しました。'); }else{ set_error('投稿に失敗しました。'); } redirect_to(BBS_URL);Read機能の実装
Createで挿入されたデータを表示されるようにしていきます。
Modelの作成
db.phpにRead機能を追記
db.phpfunction fetch_all_query($db, $sql, $params = array()){ try{ $statement = $db->prepare($sql); $statement->execute($params); return $statement->fetchAll(); }catch(PDOException $e){ set_error('データ取得に失敗しました。'); } return false; }bbs.phpに追記
bbs.php// Read function get_comments($db, $id){ $sql = " SELECT id, name, comment, created FROM bbs "; return fetch_all_query($db, $sql); }Viewの作成
bbs_view.phpに追記
- tableタグ内のtbodyタグに追記bbs_view.php<tbody> <?php foreach($bbs as $data){ ?> <tr> <td><?php print($data['name']); ?></td> <td><?php print($data['comment']); ?></td> <td><?php print($data['created']); ?></td> <td></td> </tr> <?php } ?> </tbody>Controllerの作成
bbs.php<?php require_once '../conf/const.php'; require_once MODEL_PATH. 'bbs.php'; session_start(); // SELECT文で選択したデータを表示させる為に追記 $db = get_db_connect(); $bbs = get_comments($db); include_once VIEW_PATH. 'bbs_view.php';Update機能の実装
Createで生成されたデータを変更できるようにしていきます。
Modelの作成
bbs.phpにUpdate機能を追記
bbs.php// Update function update_comment($db, $id, $comment){ $sql = " UPDATE bbs SET comment = ? WHERE id = ? "; return execute_query($db, $sql, array($comment, $id)); }Viewの作成
bbs_view.phpにUpdate処理へ遷移するよう追記
- tableタグ内のtbodyに追記bbs.php<tbody> <?php foreach($bbs as $data){ ?> <tr> <td><?php print($data['name']); ?></td> <td> <form method="post" action="bbs_update.php"> <input type="text" name="comment" value="<?php print($data['comment']); ?>"> <input type="submit" value="変更"> <!-- hiddenで該当idをpost --> <input type="hidden" name="id" value="<?php print($data['id']); ?>"> </form> </td> <td><?php print($data['created']); ?></td> <td></td> </tr> <?php } ?> </tbody>Controllerの作成
htmlの中にbbs_update.phpを作成(Update処理用)
bbs_update.php<?php require_once '../conf/const.php'; require_once MODEL_PATH. 'functions.php'; require_once MODEL_PATH. 'bbs.php'; session_start(); // データベース接続 $db = get_db_connect(); // Postされたものを定義 $id = get_post('id'); $comment = get_post('comment'); // Update if(update_comment($db, $id, $comment)){ set_message('コメントを変更しました。'); } else { set_error('コメントの変更に失敗しました。'); } redirect_to(BBS_URL);Delete機能の実装
Createで生成されたデータを削除できるようにしていきます。
Modelの作成
bbs.phpにDelete機能を追記
bbs.php// Delete function delete_comment($db, $id){ $sql = " DELETE FROM bbs WHERE id = ? "; return execute_query($db, $sql, array($id)); }Viewの作成
bbs_view.phpにDelete処理へ遷移するよう追記
- tableタグ内のtbodyに追記bbs.php<tbody> <?php foreach($bbs as $data){ ?> <tr> <td><?php print($data['name']); ?></td> <td> <form method="post" action="bbs_update.php"> <input type="text" name="comment" value="<?php print($data['comment']); ?>"> <input type="submit" value="変更"> <input type="hidden" name="id" value="<?php print($data['id']); ?>"> </form> </td> <td><?php print($data['created']); ?></td> <td> <form method="post" action="bbs_delete.php"> <input type="submit" value="削除"> <input type="hidden" name="id" value="<?php print($data['id']); ?>"> </form> </td> </tr> <?php } ?> </tbody>Controllerの作成
htmlの中にbbs_delete.phpを作成(Delete処理用)
bbs.php<?php require_once '../conf/const.php'; require_once MODEL_PATH. 'functions.php'; require_once MODEL_PATH. 'bbs.php'; session_start(); // データベース接続 $db = get_db_connect(); // Postされたものを定義 $id = get_post('id'); // Delete if(delete_comment($db, $id)){ set_message('コメントを削除しました。'); } else { set_error('コメントの削除に失敗しました。'); } redirect_to(BBS_URL);'参考
- 投稿日:2020-08-06T17:44:22+09:00
PHPでGoogle Geocoding APIを呼び出す
GCPのGeocoding APIをPHP(Guzzle)を使って呼び出してみます。
APIキーを用意する
https://console.cloud.google.com/apis/library/geocoding-backend.googleapis.com
GCPでGeocoding APIを有効化し、APIキーを発行します。PHPからAPIを呼び出す
Guzzleをインストール
HTTPクライアントのGuzzleをインストールします。
composer require guzzlehttp/guzzleAPIを呼び出す
$api_key
には発行したAPIキーの文字列を設定します。geocode.php<?php require 'vendor/autoload.php'; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; $http_client = new Client(); $url = 'https://maps.googleapis.com/maps/api/geocode/json'; $api_key = '**********************************'; /* Geocoding (latitude/longitude lookup) 住所から緯度経度を取得 */ $address = '東京都北区赤羽'; try { $response = $http_client->request('GET', $url, [ 'headers' => [ 'Accept' => 'application/json', ], 'query' => [ 'key' => $api_key, 'language' => 'ja', 'address' => $address, ], 'verify' => false, ]); } catch (ClientException $e) { throw $e; } $body = $response->getBody(); echo $body . PHP_EOL; /* Reverse geocoding (address lookup) 緯度経度から住所を取得 */ $latitude = '35.7799638'; $longitude = '139.723053'; $latlng = $latitude . ',' . $longitude; try { $response = $http_client->request('GET', $url, [ 'headers' => [ 'Accept' => 'application/json', ], 'query' => [ 'key' => $api_key, 'language' => 'ja', 'latlng' => $latlng, ], 'verify' => false, ]); } catch (ClientException $e) { throw $e; } $body = $response->getBody(); $json = json_decode($body); echo $json->results[0]->formatted_address;実行結果は次のようになります。
>php geocode.php { "results" : [ { "address_components" : [ { "long_name" : "赤羽", "short_name" : "赤羽", "types" : [ "political", "sublocality", "sublocality_level_2" ] }, { "long_name" : "北区", "short_name" : "北区", "types" : [ "locality", "political" ] }, { "long_name" : "東京都", "short_name" : "東京都", "types" : [ "administrative_area_level_1", "political" ] }, { "long_name" : "日本", "short_name" : "JP", "types" : [ "country", "political" ] }, { "long_name" : "115-0045", "short_name" : "115-0045", "types" : [ "postal_code" ] } ], "formatted_address" : "日本、〒115-0045 東京都北区赤羽", "geometry" : { "bounds" : { "northeast" : { "lat" : 35.792101, "lng" : 139.7267569 }, "southwest" : { "lat" : 35.7753371, "lng" : 139.716646 } }, "location" : { "lat" : 35.7799638, "lng" : 139.723053 }, "location_type" : "APPROXIMATE", "viewport" : { "northeast" : { "lat" : 35.792101, "lng" : 139.7267569 }, "southwest" : { "lat" : 35.7753371, "lng" : 139.716646 } } }, "place_id" : "ChIJveNASxuTGGAR2OV0KRI3zV4", "types" : [ "political", "sublocality", "sublocality_level_2" ] } ], "status" : "OK" } 日本、〒115-0045 東京都北区赤羽2丁目17−12
- 投稿日:2020-08-06T15:45:37+09:00
PHPでOption型を利用してnullチェックや例外ハンドラを消す
ScalaやRustを普段使ってる方には当たり前にあるOption型ですが、残念ながらPHPには組み込みされていません。
しかし、php-optionを利用することで同様の体験を得ることが出来ます。
https://github.com/schmittjoh/php-optionこんなコード書いてませんか?
例外を投げる
例えばRepositoryからEntityを取得する処理でこんなコードを書いてませんか?
Repositorypublic function findById(Id $id): Entity { $result = $this->queryBuilder->... if ($result === null) { throw New NotFoundException('message'); } return Entity::create($result); }データベースからデータを取得して、データがなければ例外、あればEntityとして返却するというコードを書いたことがある人もいるかと思います。
しかし、この関数を利用する際に適切に例外ハンドラを作らないと意図しないエラー画面を出力してしまう可能性があります。is_nullで値の確認
こんな処理も書いていないでしょうか?
$hoge = $entity->getHoge(); if (is_null($hoge)) { return; // デフォルトで返したい値など } return $hoge;とあるEntityから取得できるValueObject等がnullableの場合にis_nullなどを利用してチェックする処理などを書いていないでしょうか?
また、このようなチェックを見逃して意図しない値をViewに渡してしまうなどしてないでしょうか?値がある型、値がない型に分ける
Some
型とNone
型という、なにか値のある型と値がない型に分けることで処理をわかりやすくします。先程のRepositoryでのコードを例に書き換えてみます。
Repositorypublic function findById(Id $id): Option { $result = $this->queryBuilder->... if ($result === null) { return None::create(); } return Some::create(Entity::create($result)); }結果がnullだった場合はNone型、値があればSome型として返却します。
このOption型をどう使うかは次のis_nullチェックしていた例を見てみます。$hoge = $entity->getHoge(); return $hoge->getOrElse('デフォルトで返したい値など');
getHoge()
がRepositoryの例のようにOption型が返ってくると想定しています。Option型にはgetOrElse
というメソッドがあり、値があれば取得できて値がなければ引数で指定した値を返すという関数です。また、値があることだけを確認したい場合も
$hoge = $entity->getHoge(); if ($hoge->isEmpty()) { // None型 } if ($hoge->isDefined()) { // Some型 }このようにメソッドが用意されているので簡単に分岐を作ることが出来ます。
まとめ
Option型を使うことで明示的に値があるかもしれないし、無いかもしれないという型を作ってその振る舞いを扱いやすくできました。
ちなみにphp-optionはphpdotenvで利用されていて知りました。
env('KEY', {default})
はまさに値があるかもしれないし、無いかもしれないものですよね。小言
PhanとかPHPStanを使えばコメントでジェネリクスを表現してIDEでサポートされたりするんでしょうか?(知らない)
ジェネリクスを書きたい、Hackを書くしか無いか・・・
- 投稿日:2020-08-06T14:21:38+09:00
Laravel+AWS-SDK-PHPによるDynamoDBのデータ取得
背景
LaravelでDynamoDB使うにあたり、ORMのlaravel-dynamodbなどもあるが、DynamoDBでORM利用するのが少し違和感あった為にSDK使ってやってみた際のメモ(そもそもPHPのWEBフレームワークでやるなという話ではあるが、せっかくSDKが提供されているので一度使ってみようという感じ)
環境
・Windows 10
・HomeStead 9.5
・PHP 7.4
・Laravel 6.01.準備
AWS-SDK-PHPのインストール
composer require aws/aws-sdk-phpDynamoDBの環境作成
AWS提供のローカルDynamoDBを利用。Vagrantを使っていたので、Dockerと共存が面倒なことになるのでローカルに直接インストールしました。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.html※GUIツール(結構便利)
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/workbench.settingup.htmlDynamoDB起動
java -Djava.library.path=C:\localsandbox\DynamoDB\DynamoDBLocal_lib -jar C:\localsandbox\DynamoDB\DynamoDBLocal.jar -port 8001 -sharedDb※HomesteadのVirtualBoxと初期ポート8000が被るので、8001で起動させる
テスト用テーブルの作成
テーブル名:SensorLog
パーティションキー:device_id
ソートキー:receive_dtaws dynamodb create-table --table-name SensorLog --attribute-definitions AttributeName=device_id,AttributeType=S AttributeName=receive_dt,AttributeType=N --key-schema AttributeName=device_id,KeyType=HASH AttributeName=receive_dt,KeyType=RANGE --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 --endpoint-url http://localhost:8001※キーのない項目はテーブル作成時に指定できないので、テストデータ作成時に適当に作る。
※テストデータはGUIで適当に作成.envに情報設定
DYNAMODB_REGION=ap-northeast-1 #↓vagrantのlocalhost:xxxxだとつながらないのでローカルIPを設定 DYNAMODB_LOCAL_ENDPOINT=http://192.168.11.99:8001 DYNAMODB_DEBUG=true2.データ取得
・パーティションキーで絞ってデータ検索した結果の最新を取得する。
・パーティションキー+ソートキー(範囲指定)による複合条件検索の結果リストを取得するuse Aws\DynamoDb\Exception\DynamoDbException; use Aws\DynamoDb\Marshaler; use Aws\Sdk as AwsSdk; class SensorLog { /** * @var DynamoDbClient */ private $client; /** * @var Marshaler */ private $marshaler; /** * DynamoModel constructor. */ public function __construct() { // ローカルDBであっても"credentials"定義しないとエラーが吐かれるので注意 $sdk = new AwsSdk([ 'credentials' => [ 'key' => env('DYNAMODB_KEY'), 'secret' => env('DYNAMODB_SECRET'), ], 'endpoint' => env("DYNAMODB_LOCAL_ENDPOINT"), 'region' => env("DYNAMODB_REGION"), 'version' => 'latest' ]); $this->client = $sdk->createDynamoDb(); $this->marshaler = new Marshaler(); } /** * パーティションキー指定による最新データを取得する */ public function getFirst($id) { $tableName = 'SensorLog'; // 検索条件設定:device_idを検索条件にする $key_value = $id; $jsonStr = '{'.'":device_id":'.'"'.$key_value.'"'.'}'; $eav = $this->marshaler->marshalJson($jsonStr); // Limit:1 取得件数を1件に制限 // ScanIndexForward:false ソートキー(receive_dt)を降順指定 $params = [ 'TableName' => $tableName, 'KeyConditionExpression' => '#id = :device_id', 'ExpressionAttributeNames'=> [ '#id' => 'device_id' ], 'Limit' => 1, 'ScanIndexForward' => false, 'ExpressionAttributeValues'=> $eav, ]; try { $result = $this->client->query($params); $data = array(); foreach ($result['Items'] as $recdData) { $data[] = $this->marshaler->unmarshalItem($recdData); } return $data; } catch (DynamoDbException $e) { throw $e; } } /** * パーティションキー+対象日付のデータリストを取得する */ public function getDataByDateTime(int $id, string $dt) { $tableName = 'SensorLog'; // 対象日付の最小時間と最大時間を設定する // receive_dt:YYYYMMDDHHMMSSの値は数値型で格納されている $from_dt = $dt."000000"; $to_dt = $dt."235959"; // バインド変数を設定(device_id,from_dt,to_dt) $key_value = $id; $jsonStr = '{' . '":device_id":' . '"' .$key_value.'",":from_dt":'.$from_dt.',":to_dt":'.$to_dt.'}'; $eav = $this->marshaler->marshalJson($jsonStr); // device_idとreceive_dtによる範囲検索を行う // ScanIndexForward:false ソートキー(receive_dt)を降順指定 $params = [ 'TableName' => $tableName, 'KeyConditionExpression' => '#id = :device_id and receive_dt between :from_dt and :to_dt', 'ExpressionAttributeNames'=> [ '#id' => 'device_id' ], 'ScanIndexForward' => false, 'ExpressionAttributeValues'=> $eav, ]; try { $result = $this->client->query($params); $data = array(); foreach ($result['Items'] as $recdData) { $data[] = $this->marshaler->unmarshalItem($recdData); } return $data; } catch (DynamoDbException $e) { throw $e; } } }呼び出し
・コントローラーで呼び出し
//1.指定IDの最新データを1件取得 $sensorLog = $sensor->getFirst($id); //2.2020-08-06のデータを検索して結果リストを取得 $sensorLogList = $sensor->getDataByDateTime($id, "20200806"); dump($sensorLogList);参考
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GettingStarted.PHP.04.html
- 投稿日:2020-08-06T09:48:35+09:00
Adapterパターンを使うメリットとPHP実装例
※この投稿はJava言語で学ぶデザインパターン入門を読んだ感想と復習を兼ねたまとめです
Adapterパターンとは
Adapterパターンはデザインパターンのひとつで、既存のクラスを修正することなく、インタフェースを変更するための手法です。
Java言語で学ぶデザインパターン入門では、電化製品のACアダプタを例に説明されていました。家庭用のコンセントは100ボルトで固定されていますが、ノートパソコンが必要としているのは12ボルトの電源です。この「既に提供されているもの」と「いま必要としているもの」の差を吸収するのがAdapterの役割です。
Adapterパターンに登場する役割は、それぞれ次のように呼ばれるようです。
役割 意味 例 Target いま必要としているもの(インタフェース) ノートパソコンを動かすための12ボルトの電源 Adapter TargetとAdapteeの差を埋めるもの ノートパソコンと家庭用コンセントをつなぐACアダプタ Adaptee 既に提供されているもの 家庭用コンセントの100ボルトの電源 この関係をクラス図で表現すると次のようになります。
また、AdapterパターンはWrapperパターンと呼ばれることもあるようです。
継承を利用したAdapterパターン実装例
前提として、次のようなBannerクラスが既に実装されているとします。
class Banner { private $string; public function __construct(string $string) { $this->string = $string; } public function showWithParen(): void { echo '(' . $this->string . ')' . "\n"; } public function showWithAster(): void { echo '*' . $this->string . '*' . "\n"; } }Bannerは、文字列を出力する方法を2パターン(
showWithParen()
とshowWithAster()
)持っています。しかし、いま必要とされているのは次のようなインタフェースの実装です。
interface PrintInterface { public function printWeak(): void; public function printStrong(): void; }このPrintInterfaceを経由して、Bannerが持っているメソッドを呼び出す方法を考えます。
Adapterパターンには2つの実現方法がありますが、さきに紹介するのは継承を利用した方法です。
次のようなAdapterを作成します。
class PrintBanner extends Banner implements PrintInterface { public function printWeak(): void { $this->showWithParen(); } public function printStrong(): void { $this->showWithAster(); } }上記のPrintBannerは、既に実装されているBannerを継承して、いま必要とされているPrintInterfaceを実装しています。このようなAdapterを間にはさむことによって、既存のクラスを変更することなく、呼び出し元はインタフェース経由で目的の関数を呼び出すことが可能になります。
委譲を利用したAdapterパターン実装例
Adapterパターンを実現するもうひとつの方法は、委譲を利用する方法です。
委譲という言葉は知りませんでしたが、利用したいクラスのインスタンスを生成し、そのインスタンスを経由して目的の処理を実行することを委譲というようです。クラス図で表現すると次のようになります。
委譲を利用する方法は、Targetがインタフェースではなくクラスである場合に便利です。
// Targetがクラスである場合 abstract class PrintAbstract { abstract public function printWeak(): void; abstract public function printStrong(): void; }さきほどはAdpterがAdapteeを継承することでAdapterパターンを実現できましたが、複数のクラスを同時に継承することはできないため、Targetがクラスである場合にはそれが使えません。代わりに、委譲を利用して次のように実現します。
class PrintBanner extends PrintAbstract { private $banner; // Bannerをプロパティとして持つ public function __construct(string $string) { $this->banner = new Banner($string); } public function printWeak(): void { $this->banner->showWithParen(); // 委譲を利用してBannerの関数を呼び出す } public function printStrong(): void { $this->banner->showWithAster(); } }Adapterパターンのメリット
Adapterパターンは、既存のクラスを修正することなく、インタフェースを変更するための手法です。
既存のクラスを使い回すことのメリットには、次のようなものがあるようです。
- ゼロから実装するよりも早く完成する
- 既存のクラスがテスト済みである場合は、確実に動作するものを使いまわせる
- バグが発生した場合は、Adapter側に原因があることが明らかなので修正も容易
- 既存のクラスは修正しないため、テストを書き直す必要がない
- 委譲を利用すればTargetがクラスである場合も他のクラスの実装を利用できる
参考までに、Adapterパターン実装例(PHP)を載せておきます。
https://github.com/yuta-o-note/gof-design-pattern
<?php /** * 継承を利用して実現するパターン */ interface PrintInterface { public function printWeak(): void; public function printStrong(): void; } class Banner { private $string; public function __construct(string $string) { $this->string = $string; } public function showWithParen(): void { echo '(' . $this->string . ')' . "\n"; } public function showWithAster(): void { echo '*' . $this->string . '*' . "\n"; } } class PrintBanner extends Banner implements PrintInterface { public function printWeak(): void { $this->showWithParen(); } public function printStrong(): void { $this->showWithAster(); } } function main(PrintInterface $print) { $print->printWeak(); $print->printStrong(); } $print = new PrintBanner('Hello'); main($print);<?php /** * 委譲を利用して実現するパターン */ abstract class PrintAbstract { abstract public function printWeak(): void; abstract public function printStrong(): void; } class Banner { private $string; public function __construct(string $string) { $this->string = $string; } public function showWithParen(): void { echo '(' . $this->string . ')' . "\n"; } public function showWithAster(): void { echo '*' . $this->string . '*' . "\n"; } } class PrintBanner extends PrintAbstract { private $banner; public function __construct(string $string) { $this->banner = new Banner($string); } public function printWeak(): void { $this->banner->showWithParen(); } public function printStrong(): void { $this->banner->showWithAster(); } } function main() { $print = new PrintBanner('Hello'); $print->printWeak(); $print->printStrong(); } main();
- 投稿日:2020-08-06T00:05:16+09:00
PHP 条件演算子(三項演算子)の省略構文「??」
目的
- 先輩方が記載したコードを読んでいて初めて知ったものだったのでまとめる
三項演算子
??
- この演算子は別名null合体演算子とも呼ばれる。
下記に例を記載する。
式1 ?? 式2;式1がnullではない場合には式1が実行される。
式1がnullの時は式2が実行される。
具体例
- 変数$strに文字列を格納する処理を記載する。
- 変数$base_strに文字列が格納されている時は変数$base_strの文字列を変数$strに格納する。
変数$base_strがnullの時は文字列「base_str is null」を変数$strに格納する。
$str = $base_str ?? 'base_str is null';