- 投稿日:2020-01-23T23:27:47+09:00
CakePHPとAjaxでいいねボタンをつくりました
1週間前のAjax何もわかってない自分に向けて書きます。
CakePHP3.8を使っています。つくりたいもの
Twitterのいいねボタンみたいなやつ。
掲示板の投稿メッセージ一覧に、メッセージに付随して表示する。
自分がボタンをすでに押している場合は、ボタンに色がついている。押していない場合、色がついていない。
他ユーザー含めて、いいねボタンが押された数を表示する。まず
jQueryを勉強するところから始めました。
テーブルの構成
関係あるところらへんだけ
- Users
- id (int)
- username(関係ない)
- password(関係ない)
- Posts
- id (int)
- message(関係ない)
- Favorites
- id (int)
- user_id (int)
- post_id (int)
とりあえずメッセージ一覧画面に、いいねボタンといいね数を表示する
関係ありそうなところだけ抜粋して載せています。
色々と省略しています。PostsTable.phppublic function initialize(array $config) { // PostsとFavoritesは1対多 $this->hasMany('Favorites', [ 'foreignKey' => 'post_id', ]); } // 投稿メッセージを一覧表示するのに使います // メッセージごとのいいねの数も一緒に取得しています public function findBbsPosts() { $query = $this->find(); $query ->contain(['Users', 'Favorites']) ->select(['favorites_count' => $query->func()->count('Favorites.id')]) ->leftJoinWith('Favorites') ->group(['Posts.id']) ->enableAutoFields(true); return $query; }FavoritesTable.phppublic function initialize(array $config) { $this->belongsTo('Users', [ 'foreignKey' => 'user_id', 'joinType' => 'INNER', ]); $this->belongsTo('Posts', [ 'foreignKey' => 'post_id', 'joinType' => 'INNER', ]); } // 投稿メッセージごとのいいねの数をカウントするのに使います public function countFavorite(int $post_id) { $query = $this->find(); $query ->where(['post_id' => $post_id]); return $query; }とりあえず投稿メッセージを一覧表示させます。
$authuser
にログインしているユーザーについての情報を入れています。PostsController.phppublic function initialize() { // 色々と省略 $this->loadComponent('Paginator'); $this->set('authuser', $this->Auth->user()); } public function index() { $bbsPosts = $this->paginate($this->Posts->findBbsPosts(), [ 'order' => ['Posts.id' => 'DESC'], ]); $this->set(compact('bbsPosts')); }投稿メッセージをelementに切り出しています。
Posts/index.ctp<?php foreach ($bbsPosts as $bbsPost) : ?> <?= $this->element('article', ['bbsPost' => $bbsPost]) ?> <?php endforeach; ?>さらに
article.ctp
の中でもいいねボタンに関連する部分だけelementに切り出しています。article.ctp<?= $this->element('favorite', ['bbsPost' => $bbsPost]) ?>すごく格好悪いのですが、
array_search
を使って、「ログインしているユーザのID」と、「投稿メッセージごとにくっついているFavoritesのuser_id」で一致するものがあるかどうか探しにいきます。(効率が良くないです。)
- 一致するものがあれば、
favorite-add-button
を非表示- 一致するものがなければ、
favorite-delete-button
を非表示- 非表示は、フォームを囲むdivのclassにhideを追加しています。
ハートマークの表示の違いは、Font Awesomeを使っています。
ボタンのclassのfas fa-heart
とfar fa-heart
で変えています。ついでに、フォームを囲むdivのid属性に、投稿メッセージごとのPostsのidをつけています。
favorite.ctp<!-- まず、ログインユーザーが投稿ごとにいいねボタンを押しているかどうかを確認します --> <?php $searchFavorite = array_search($authuser['id'], array_column($bbsPost->favorites, 'user_id'), true) ?> <!-- 一致するものがなければ、まだ押していないので、「add」のフォームを表示 --> <?php if ($searchFavorite === false) : ?> <div class="favorite" id="addfavorite<?php echo h($bbsPost->id) ?>"> <!-- 一致するものがあれば、すでに押しているので、「add」のフォームを非表示(hide追加) --> <?php else : ?> <div class="favorite hide" id="addfavorite<?php echo h($bbsPost->id) ?>"> <?php endif; ?> <?= $this->Form->create('', [ 'url' => [ 'controller' => 'Favorites', 'action' => 'add', ], 'class' => 'favorite-form' ]) ?> <?php echo $this->Form->hidden('user_id', ['value' => $authuser['id']]); echo $this->Form->hidden('post_id', ['value' => $bbsPost->id]); ?> <?= $this->Form->button('', [ 'class' => 'far fa-heart favorite-add-button', ]) ?> <?= $this->Form->end() ?> </div> <!-- 一致するものがあれば、すでに押しているので、「delete」のフォームを表示 --> <?php if ($searchFavorite !== false) : ?> <div class="favorite" id="deletefavorite<?php echo h($bbsPost->id) ?>"> <!-- 一致するものがなければ、まだ押していないので、「delete」のフォームを非表示(hide追加) --> <?php else : ?> <div class="favorite hide" id="deletefavorite<?php echo h($bbsPost->id) ?>"> <?php endif; ?> <?= $this->Form->create('', [ 'url' => [ 'controller' => 'Favorites', 'action' => 'delete', ], 'class' => 'favorite-form', ]) ?> <?php echo $this->Form->hidden('user_id', ['value' => $authuser['id']]); echo $this->Form->hidden('post_id', ['value' => $bbsPost->id]); ?> <?= $this->Form->button('', [ 'class' => 'fas fa-heart favorite-delete-button', ]) ?> <?= $this->Form->end() ?> </div>css.favorite.hide { display: none; }カウント部分は
PostsTable.php
でselect(['favorites_count' => $query->func()->count('Favorites.id')])
として取得したものを使います。favorite.ctp<p class="favCount" id="favCount<?php echo h($bbsPost->id) ?>"> <?php if (! isset($bbsPost->favorites_count)) : ?> 0 <?php else : ?> <?= $this->Number->format($bbsPost->favorites_count) ?> <?php endif; ?> </p>Ajaxを使ってハートマークとカウント数を変化させます
コントローラでデータベースに保存したり削除したりします。
FavoritesController.phppublic function add() { $this->autoRender = false; $favorite = $this->Favorites->newEntity(); if ($this->request->is('ajax')) { $received_data = $this->request->getData(); $favorite->user_id = $this->request->getData('user_id'); $favorite->post_id = $this->request->getData('post_id'); if ($this->Favorites->save($favorite)) { // FavoritesTable.phpに書いたcountFavorite()を使っていいねの数を取得します $count = $this->Favorites->countFavorite($favorite->post_id)->count(); // favorite-button-change.jsに'received_data'と'count'を渡します $this->response->body(json_encode(['received_data' => $received_data, 'count' => $count])); return; } $this->Flash->error(__('The favorite could not be saved. Please, try again.')); } } public function delete() { $this->autoRender = false; if ($this->request->is('ajax')) { $received_data = $this->request->getData(); $param = [ 'user_id' => $this->request->getData('user_id'), 'post_id' => $this->request->getData('post_id'), ]; if ($this->Favorites->deleteAll($param)) { $count = $this->Favorites->countFavorite($this->request->getData('post_id'))->count(); $this->response->body(json_encode(['received_data' => $received_data, 'count' => $count])); return; } else { $this->Flash->error(__('The favorite could not be deleted. Please, try again.')); } } }favorite-button-change.js$(function () { // 「add」のボタンをクリックしたとき $('.favorite-add-button').click(function (event) { event.preventDefault(); var param = $(this).parent('.favorite-form').serializeArray(); $.ajax({ url: '/bbs/Favorites/add', // FavoritesControllerのaddアクションに送ります type: 'POST', dataType: 'json', data: param, timeout: 10000, }).done(function (result) { // 成功の場合 // FavoriteControllerのaddアクションのreturnの結果がresultに入っています // divのclass属性のhideを逆にします $('#addfavorite' + result['received_data']['post_id']).addClass('hide'); $('#deletefavorite' + result['received_data']['post_id']).removeClass('hide'); // いいね数を取得した数に書き換えます $('#favCount' + result['received_data']['post_id']).text(result['count']); }).fail(function (XMLHttpRequest, textStatus, errorThrown) { // 失敗の場合 alert("失敗"); }); }); // 「delete」のボタンをクリックしたとき $('.favorite-delete-button').click(function (event) { event.preventDefault(); var param = $(this).parent('.favorite-form').serializeArray(); $.ajax({ url: '/bbs/Favorites/delete', type: 'POST', dataType: 'json', data: param, timeout: 10000, }).done(function (result) { $('#addfavorite' + result['received_data']['post_id']).removeClass('hide'); $('#deletefavorite' + result['received_data']['post_id']).addClass('hide'); $('#favCount' + result['received_data']['post_id']).text(result['count']); }).fail(function (XMLHttpRequest, textStatus, errorThrown) { alert("失敗"); }); }); });拙い出来なので、ご指摘いただけるととてもありがたいです。
- 投稿日:2020-01-23T22:52:52+09:00
【初学者向け】セキュリティ対策入門[番外編①]〜OSコマンドインジェクション編〜
前提
確認環境
以下と同様です。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜本シリーズの目的
以下と同様です。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜本記事の目標
OSコマンドインジェクションの概要、原因、対策について理解することです。
本記事を読み進める上での必要事項
以下の内容を終えていることです。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜どうでもいいお話
あまり主要ではない脆弱性についてはこちらの番外編で扱っていきます。主要ではないというのは被害ケースをあまり耳にしないという意味で、脆弱性をつかれたときの被害が少ないというわけではないので悪しからず。
概要
OSコマンドについて不正な命令を入れ込むことです。こちらのSQLインジェクションがSQL文について不正な命令を入れ込むことならば、こちらはOSコマンドというわけです。
実際に見てみましょう。とはいえ、番外編なのでGitHubにソースはないので本記事上で紹介します。
実際に見てみよう
<?php $file_list = shell_exec("ls {$_GET['param']}"); echo $file_list;PHPファイルでOSコマンドを実行したい場合、
shell_exec
という関数を使う必要があります。では、実際にどういった場合に被害が発生するかというと、
| {任意のOSコマンド}
のような文字列をクエリストリングスのparam
に与えられたときです。OSコマンドについて学んだことがある方なら、これのやばさが伝わると思います。自由なOSコマンドが使えれば任意のファイルを書き換えたり削除したり様々な被害をもたらすことが可能です。めちゃくちゃ恐ろしいですね。
原因
入力値をそのままOSコマンドの一部に渡していることです。
対策
そもそも使わない
OSコマンドをユーザの入力で行わなければいけないケースというのはなかなかないはずです。あったとしてもライブラリを使用するなどの選択肢を検討する必要があります。
そもそも使わない観点では、
php.ini
で特定の関数を実行不可にできるので本対策を行う場合はそれも合わせて行った方がいいでしょう。エスケープする
上述の通り、こういった処理は実装しないのが一番です。とはいえ、どうしても実装したい場合はエスケープする関数もあります。
escapeshellarg
という関数です。具体的にすると以下のような感じです。<?php $param = escapeshellarg($_GET['param']); $file_list = shell_exec("ls {$param}"); echo $file_list;参考文献
今回の内容は以上です。最後までご覧いただきありがとうございました。
- 投稿日:2020-01-23T22:52:52+09:00
【初学者向け】セキュリティ対策入門[番外編]①〜OSコマンドインジェクション編〜
前提
確認環境
以下と同様です。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜本シリーズの目的
以下と同様です。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜本記事の目標
OSコマンドインジェクションの概要、原因、対策について理解することです。
本記事を読み進める上での必要事項
以下の内容を終えていることです。
【初学者向け】セキュリティ対策入門⓪〜環境構築編〜どうでもいいお話
あまり主要ではない脆弱性についてはこちらの番外編で扱っていきます。主要ではないというのは被害ケースをあまり耳にしないという意味で、脆弱性をつかれたときの被害が少ないというわけではないので悪しからず。
概要
OSコマンドについて不正な命令を入れ込むことです。こちらのSQLインジェクションがSQL文について不正な命令を入れ込むことならば、こちらはOSコマンドというわけです。
実際に見てみましょう。とはいえ、番外編なのでGitHubにソースはないので本記事上で紹介します。
実際に見てみよう
<?php $file_list = shell_exec("ls {$_GET['param']}"); echo $file_list;PHPファイルでOSコマンドを実行したい場合、
shell_exec
という関数を使う必要があります。では、実際にどういった場合に被害が発生するかというと、
| {任意のOSコマンド}
のような文字列をクエリストリングスのparam
に与えられたときです。OSコマンドについて学んだことがある方なら、これのやばさが伝わると思います。自由なOSコマンドが使えれば任意のファイルを書き換えたり削除したり様々な被害をもたらすことが可能です。めちゃくちゃ恐ろしいですね。
原因
入力値をそのままOSコマンドの一部に渡していることです。
対策
そもそも使わない
OSコマンドをユーザの入力で行わなければいけないケースというのはなかなかないはずです。あったとしてもライブラリを使用するなどの選択肢を検討する必要があります。
そもそも使わない観点では、
php.ini
で特定の関数を実行不可にできるので本対策を行う場合はそれも合わせて行った方がいいでしょう。エスケープする
上述の通り、こういった処理は実装しないのが一番です。とはいえ、どうしても実装したい場合はエスケープする関数もあります。
escapeshellarg
という関数です。具体的にすると以下のような感じです。<?php $param = escapeshellarg($_GET['param']); $file_list = shell_exec("ls {$param}"); echo $file_list;参考文献
今回の内容は以上です。最後までご覧いただきありがとうございました。
- 投稿日:2020-01-23T21:24:05+09:00
videoタグがipadでうまく再生できない考察
はじめに
動画ファイルがたくさん溜まってきたのでwebで検索し再生するソフトを作成した。html5からvideoタグが使えるようになり、新しいwebの作り方を勉強しつつ作成したものです。しかし、パソコン・Androidスマホでwebを操作し再生することはできたのだが、ipadで再生することができなかった。はじめてのvideoでもあり、使い方に間違いがあるのかもしれませんが、結局原因がわかりませんでした。
今回、調査した内容をまとめるとともに、当面の運用として対処法を示す。
使用したプログラム
原因調査をするため、プログラムをシンプルにした。プログラムはhtmlファイルとビデオを提供するphpファイルです。
対象動画ファイル(003.mp4):サイズは1280x720、長さは2:04:33、ファイルサイズは1.33GiB
大きいファイルサイズの動画が対象のため、一度に全てを送信するにはサーバの負担が大きすぎるため、最大1MBに制限しクライアントから逐次複数回要求により全体を提供する方法とした。index.html<html lang='ja'> <head> <meta http-equiv='content-type' content='application/html; charset=EUC-JP' > </head> <body> <div id='video'> <video controls width="640" autoplay playsinline> <source src="movie_mp4t.php"> </video> </div> </body> </html>movie_mp4t.php<?php function write_log($text) { umask(0); $log = date("Y-m-d H:i:s") . " {$_SERVER['REQUEST_URI']} {$text}\n"; error_log($log, 3, "access.log"); } $file = "003.mp4"; // 動画ファイルへのパス $size = filesize($file); $fp = fopen($file, "rb"); $step = 1000000; // 最大伝送サイズ if (@$_SERVER["HTTP_RANGE"]){ // ブラウザがHTTP_RANGEを要求してきた場合 list($start, $end) = sscanf($_SERVER["HTTP_RANGE"], "bytes=%d-%d"); // 要求された開始位置と終了位置を取得 $s = $end - $start + 1; write_log('$_SERVER["HTTP_RANGE"]:'.$_SERVER["HTTP_RANGE"]); if (empty($end)) $end = $start + $step - 1; // 終了位置が指定されていない場合$step bytes出す fseek($fp, $start); } else { $start = 0; $end = $step - 1; } if ($end - $start >= $step) $end = $start + $step - 1; // 要求が$stepより多い場合$stepに制限 if ($end >= $size - 1) $end = $size - 1; // 要求が動画の終了を超えている場合制限 $c_size = $end - $start + 1; // 提供サイズ $etag = md5($_SERVER["REQUEST_URI"]).$size; // コンテンツの識別子 $header_http = "HTTP/1.1 206 Partial Content"; $header_content = "Content-Type: video/mp4"; $header_accept = "Accept-Ranges: bytes"; $header_range = "Content-Range: bytes {$start}-{$end}/{$size}"; $header_length = "Content-Length: {$c_size}"; $header_etag = "Etag: \"{$etag}\""; header($header_http); write_log($header_http); header($header_content); write_log($header_content); header($header_accept); // HTTP_RANGE(部分リクエスト)に対応 write_log($header_accept); header($header_range); write_log($header_range); header($header_length); write_log($header_length); header($header_etag); write_log($header_etag); if ($c_size) echo fread($fp, $c_size); // ファイルポインタの開始位置からコンテンツ長だけ出力 fclose($fp); ?>状況
以下のログのとおり、複数回のやり取り後止まってしまいます。
ログが長いので固定メッセージを省略し示す。access.log2020-01-15 08:22:06 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=0-1 2020-01-15 08:22:06 /movie4/movie_mp4t.php HTTP/1.1 206 Partial Content 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Type: video/mp4 2020-01-15 08:22:06 /movie4/movie_mp4t.php Accept-Ranges: bytes 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Range: bytes 0-1/1432153772 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Length: 2 2020-01-15 08:22:06 /movie4/movie_mp4t.php Etag: "c7ad0632263096871e3aaf236ccf7e301432153772" 2020-01-15 08:22:06 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=0-1432153771 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Range: bytes 0-999999/1432153772 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:06 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1423966208-1432153771 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Range: bytes 1423966208-1424966207/1432153772 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:06 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1427505152-1432153771 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Range: bytes 1427505152-1428505151/1432153772 2020-01-15 08:22:06 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:07 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1432092672-1432153771 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Range: bytes 1432092672-1432153771/1432153772 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Length: 61100 2020-01-15 08:22:07 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1424228320-1427505151 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Range: bytes 1424228320-1425228319/1432153772 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:07 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1427800032-1432092671 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Range: bytes 1427800032-1428800031/1432153772 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:07 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1425228320-1427505151 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Range: bytes 1425228320-1426228319/1432153772 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:07 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1426228320-1427505151 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Range: bytes 1426228320-1427228319/1432153772 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:07 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1427228320-1427505151 2020-01-15 08:22:07 /movie4/movie_mp4t.php HTTP/1.1 206 Partial Content 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Length: 276832 2020-01-15 08:22:07 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1428800032-1432092671 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Range: bytes 1428800032-1429800031/1432153772 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:07 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1429800032-1432092671 2020-01-15 08:22:07 /movie4/movie_mp4t.php HTTP/1.1 206 Partial Content 2020-01-15 08:22:07 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:08 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1430800032-1432092671 2020-01-15 08:22:08 /movie4/movie_mp4t.php Content-Range: bytes 1430800032-1431800031/1432153772 2020-01-15 08:22:08 /movie4/movie_mp4t.php Content-Length: 1000000 2020-01-15 08:22:08 /movie4/movie_mp4t.php $_SERVER["HTTP_RANGE"]:bytes=1431800032-1432092671 2020-01-15 08:22:08 /movie4/movie_mp4t.php Content-Range: bytes 1431800032-1432092671/1432153772 2020-01-15 08:22:08 /movie4/movie_mp4t.php Content-Length: 292640考察
ログを以下のように整理する。
14回の要求で動画のindexを読み終えた段階で停止している。最大サイズ1MB要 求 応 答 転 送 ---------- ---------- ---------- ---------- ------- ---------------------------------- 1: 0- 1 0- 1 2 ファイルの識別子?2バイト読み込み 2: 0-1432153771 0- 999999 1000000 ファイル全体を要求されるが1MB送信 3: 1423966208-1432153771 1423966208-1424966207 1000000 動画のindex全体を要求されるが1MB送信 4: 1427505152-1432153771 1427505152-1428505151 1000000 動画のindex 5: 1432092672-1432153771 1432092672-1432153771 1000000 動画のindex 6: 1424228320-1427505151 1424228320-1425228319 1000000 動画のindex 7: 1427800032-1432092671 1427800032-1428800031 1000000 動画のindex 8: 1425228320-1427505151 1425228320-1426228319 1000000 動画のindex 9: 1426228320-1427505151 1426228320-1427228319 1000000 動画のindex 10: 1427228320-1427505151 1427228320-1427505151 276832 動画のindex 11: 1428800032-1432092671 1428800032-1429800031 1000000 動画のindex 12: 1429800032-1432092671 1429800032-1430800031 1000000 動画のindex 13: 1430800032-1432092671 1430800032-1431800031 1000000 動画のindex 14: 1431800032-1432092671 1431800032-1432092671 292640 動画のindex転送最大サイズを変更して実施してみる。
最大サイズ2MB要 求 応 答 転 送 ---------- ---------- ---------- ---------- ------- ---------------------------------- 1: 0- 1 0- 1 2 ファイルの識別子?2バイト読み込み 2: 0-1432153771 0- 1999999 2000000 ファイル全体を要求されるが2MB送信 3: 1423966208-1432153771 1423966208-1425966207 2000000 動画のindex全体を要求されるが2MB送信 4: 1427505152-1432153771 1427505152-1429505151 2000000 動画のindex 5: 1432092672-1432153771 1432092672-1432153771 61100 動画のindex 6: 1424203752-1427505151 1424203752-1426203751 2000000 動画のindex 7: 1427726312-1432092671 1427726312-1429726311 2000000 動画のindex 8: 1426203752-1427505151 1426203752-1427505151 1301400 動画のindex 9: 1429726312-1432092671 1429726312-1431726311 2000000 動画のindex 10: 1431726312-1432092671 1431726312-1432092671 366360 動画のindex最大サイズ3MB要 求 応 答 転 送 ---------- ---------- ---------- ---------- ------- ---------------------------------- 1: 0- 1 0- 1 2 ファイルの識別子?2バイト読み込み 2: 0-1432153771 0- 2999999 3000000 ファイル全体を要求されるが3MB送信 3: 1423966208-1432153771 1423966208-1426966207 3000000 動画のindex全体を要求されるが3MB送信 4: 1427505152-1432153771 1427505152-1430505151 3000000 動画のindex 5: 1432092672-1432153771 1432092672-1432153771 61100 動画のindex 6: 1424179176-1427505151 1424179176-1427179175 3000000 動画のindex 7: 1427677168-1432092671 1427677168-1430677167 3000000 動画のindex 8: 1427179176-1427505151 1427179176-1427505151 325976 動画のindex 9: 1430677168-1432092671 1430677168-1432092671 1415504 動画のindex最大サイズ4MB要 求 応 答 転 送 ---------- ---------- ---------- ---------- ------- ---------------------------------- 1: 0- 1 0- 1 2 ファイルの識別子?2バイト読み込み 2: 0-1432153771 0- 3999999 4000000 ファイル全体を要求されるが4MB送信 3: 1423966208-1432153771 1423966208-1427966207 4000000 動画のindex全体を要求されるが4MB送信 4: 1427505152-1432153771 1427505152-1431505151 4000000 動画のindex 5: 1432092672-1432153771 1432092672-1432153771 61100 動画のindex 6: 1424277472-1427505151 1424277472-1427505151 3227680 動画のindex 7: 1427767264-1432092671 1427767264-1431767263 4000000 動画のindex 8: 1431767264-1432092671 1431767264-1432092671 325408 動画のindex 9: 196584-1423966207 196584- 4196583 4000000 動画 10: 4196584-1423966207 4196584- 8196583 4000000 動画最後の最大サイズ4MBにすることで正常に動画が再生された。
何れの最大サイズにおいても3:~5:の要求が全く同じで応答レンジとは全く関係がない。
正常に再生されない最大サイズでは、3:と4:の隙間を6:以降で補完しているが、4:と5:の補完が完全に行えていなく再生に失敗していると思われる。最大サイズ4MBの4:と5:には偶然(?)隙間がなく、再生できた。更に、サイズの大きな動画ファイルでは最大サイズ4MBでも再生に失敗することもわかっている。
ここでは、紹介しなかったがプログレッシブダウンロード対応のMP4ファイル(indexをファイルの前方に配置する)を作成し実験したが変化はなかった。結論
原因については、ipad-osのバグと考えたほうが自然では有るが、これまでに対応されていないところを見るとなにか理由がありそうである。結局原因は全く分からず対処法として以下で対応している。
動画サイズによって、変動することから最大サイズを動画サイズの1/300とすることで取り敢えず再生できている。- $step = 1000000; // 最大伝送サイズ + $step = round($size / 300); // 最大伝送サイズ因みに、PCで再生した場合を以下に示す。若干クセがありますが、順次読み取っていくシンプルな読み方となっています。
最大サイズ1MB(PC)要 求 応 答 転 送 ---------- ---------- ---------- ---------- ------- ---------------------------------- 1: 0- 0- 999999 1000000 全体要求 2: 1423966208-1432153771 1423966208-1424966207 1000000 動画のindex全体を要求されるが1MB送信 3: 1424966208- 1424966208-1425966207 1000000 動画のindex 4: 1425966208-1432153771 1425966208-1426966207 1000000 動画のindex 5: 1426966208- 1426966208-1427966207 1000000 動画のindex 6: 1427966208-1432153771 1427966208-1428966207 1000000 動画のindex 7: 1428966208- 1428966208-1429966207 1000000 動画のindex 8: 1429966208-1432153771 1429966208-1430966207 1000000 動画のindex 9: 1430966208- 1430966208-1431966207 1000000 動画のindex 10: 1431966208-1432153771 1431966208-1432153771 187564 動画のindex 11: 0-1430966271 0- 999999 1000000 動画(?) 12: 0-1432153771 0- 999999 1000000 動画 13: 1000000- 1000000- 1999999 1000000 動画 14: 2000000-1432153771 2000000- 2999999 1000000 動画 15: 3000000- 3000000- 3999999 1000000 動画参考
- 投稿日:2020-01-23T19:58:26+09:00
LaravelでUncaught domexception: failed to execute '***' on 'element': ',' is not a valid attribute name.を吐き出したときに確認したこと
Laravelにてアプリを作成していたところコンソールに下記のようなエラーが発生
Uncaught domexception: failed to execute 'setattribute' on 'element': ',' is not a valid attribute name.簡単に訳すと「属性名に','(カンマ)は使えないから'setattribute'は実行できないよ!」
と怒られてしまってます。考えたこと
コンソールのエラーだからJavaScriptのファイルに不備があるのか?
右側のapp.js:41954から該当エラー個所をみてみるが何もおかしいところはなさそう。
そもそも","(カンマ)がその行にない...「属性名」に異常?つまりviewファイル側におかしいところがあるのか?
index.blade.php<li> <a href="{{ action('WordsController@show', $word )}}">{{ $word->en }}</a> <a href="">{{ $word->ja }}</a> <a href="#", data-id="{{ $word->id }}">[×]</a> <form method="post", action=" {{ url('/words', $word->id) }} " id="form_{{ $word->id }}" > {{ csrf_field() }} {{ method_field('delete') }} </form> </li>よーくみると
4行目のhref="#"の後ろのカンマ...
5行目のmethod="post"の後ろのカンマ...いりません!!!
これを消したらエラーも消えました。
おわりに
認証機能を追加するべく、Laravel6のuiパッケージを入れ込んでからこのエラーがで始めたので、色々と考え込んでしまったけれど、結局は小さなミスでした。
直前までUIも問題なく動いていたのも時間を食ってしまいました。
しかしながら探しても日本語の記事が全然ない...
同じようなことが起こった方と自分への忘備録として残しておきます。参考リンク
- 投稿日:2020-01-23T17:52:49+09:00
【初心者向け】関数について
意味 関数 一つの処理をまとめたもの 引数 関数に渡せる値 返り値 関数から戻ってくる値 初期値 引数がない時の値 関数とは
処理をひとまとめにすることで使いまわせる
似たような処理をまとめられる//定義 function sayHello() { //処理 echo "Hello!"; } //呼び出し sayHello();// Hello! sayHello();// Hello! 何度も呼び出し可能引数とは
関数に渡す値
//渡された引数が$nameに入る function sayHello($name) { //$nameは関数の中で使用できる echo "Hello!" . $name; } //関数に引数を渡す sayHello("Tom");// Hello!Tom sayHello("Bob");// Hello!Bob //引数は複数指定できる function sayHello($name,$ago) { echo "Hello!" . $name . $ago; } sayHello("Tom",19);// Hello!Tom19 sayHello("Bob",20);// Hello!Bob20返り値
処理を終了させ、関数から戻ってくる値
function sayHello($name) { //値を返す return "Hello!" . $name; echo "ここは処理をされない。"; } $test = "jon"; //sayHello関数の呼び出し echo sayHello($test);// Hello!jon初期値
引数を指定しなった時の値
//初期値指定 function sayHello($name = "Jon") { echo "Hello!" . $name; } sayHello("TOM");// Hello!Tom sayHello("Bob");// Hello!Bob sayHello(); // Hello!Jon 初期値
- 投稿日:2020-01-23T16:59:11+09:00
AmazonLinuxでPHP7.1から7.2にアップデート
概要
AmazonLinuxでPHP7.1から7.2にあげたときのメモです。
試行錯誤しながらやったので、これ通りに動かないことがあると思います。
- yumからインストール
$ sudo yum -y install php72 php72-mbstring php72-pdo php72-php-fpm.x86_64実行結果
: (中略) Error Requires: scl-utils
- scl-utilsが必要らしいので、インストール
- yumでは用意されていないので、rpmから
$ sudo rpm -Uvh ftp://ftp.scientificlinux.org/linux/scientific/6.9/x86_64/updates/fastbugs/scl-utils-20120927-29.el6_9.x86_64.rpm
- 再度php72をインストール
$ sudo yum -y install php72 php72-mbstring php72-pdo php72-php-fpm.x86_64
- 新しいバージョンに切替
$ sudo unlink /usr/bin/php $ sudo ln -s /usr/bin/php72 /usr/bin/php $ sudo /etc/init.d/php-fpm stop && sudo /etc/init.d/php72-php-fpm start
- (Option) composerで必要なpackageを追加でインストール
$ sudo yum install php72-php-mbstring.x86_64 php72-php-pdo.x86_64 php72-php-xml.x86_64 php72-php-mysqlnd困ったときに実行すると良いコマンド
- インストールできるpackageを確認
$ yum list | grep php
- インストール済みpackage確認
$ yum list installed | grep php
- php-fpmリスタート
$ sudo service php72-php-fpm restart
- nginxリスタート
$ sudo service nginx restart
- php.iniの場所確認
$ php -r "echo phpinfo();" | grep "php.ini"あとがき
Dockerにしたい、、
参考
https://qiita.com/kidatti/items/2d6a4a24f89dc71eb66e
https://skworkspace.net/archives/457
- 投稿日:2020-01-23T15:01:24+09:00
Laravelでブログの本文の続きを「...」にする方法
{!! nl2br(e(str_limit(変数, 制限文字数))) !!}
で制限文字数から先の文章は「...」になります。
解説
nl2br
PHPの改行コードになります。(htmlで言う
<br>
)
https://www.php.net/manual/ja/function.nl2br.phpe()
()内をエスケープさせます。
e関数は、PHPのhtmlspecialchars関数をdouble_encodeオプションにデフォルトでtrueを指定し、実行します。
https://readouble.com/laravel/5.7/ja/helpers.html (「e()」で文字検索してください。)
htmlspecialchars — 特殊文字を HTML エンティティに変換する
https://www.php.net/manual/ja/function.htmlspecialchars.php
エスケープ処理に関してはこちらを参照
https://qiita.com/n_hirai/items/df0a21d2409ee47973e5まあ要は記号を特殊文字に変換させることです。そうすることによってユーザーが意図せず内部のプログラミングをいじってしまうことを防ぐことができます。
例えば、phpで$this->hensuu
の値を取得する処理が書かれている場合、フォームでユーザーが「あああ<<<超えられない壁<<<いいい」とか打ってしまったら内部のプログラミングが<
に反応してバグったらやばいからこの処理を使うのです。(もっと良い例えありませんか・・・?)str_limit(変数、制限文字)
Str::limit
でも行けます。繰り返しになってしまいますが、制限文字数から先の文章は「...」になります。
なんか最初からこうなってるみたいです。str_limit関数は、指定した長さへ文字列を切り詰めます。
https://readouble.com/laravel/5.7/ja/helpers.html (「str_limit」で文字検索してください。)
{!! !!}
これでエスケープから逃れることができます。
通常Laravelで使われるblade構文の{}
はhtmlからエスケープしてphp処理を行う際に使われるものでした。
それを「!!」にすることで否定系にしているのでhtmlとして処理されます。
中の処理が終了したらhtmlとして出力させようとしています。個人的に
なにこれ解説むっず。本当に合ってんのかこれ。
でも使ってみると結構便利なので使ってみてください。何かあればコメントお願いします><
- 投稿日:2020-01-23T14:52:38+09:00
Googleスプレッドシートの内容を読み込む
はじめに
こちらの記事は、
を使用してGoogleスプレッドシートの内容を読み込むサンプルです。
参考にした記事は、PHPでSheetsAPIを使用してGoogleスプレッドシートにデータを追記していくサンプル。です。
↑こちらの記事は、スプレッドシートの書き込みですが、この記事では読み込み方法を説明します。
ですので、上記の記事の
- プロジェクトを作る。
- スプレッドシート作成・共有設定
- PHPからスプレッドシート追記 > 0. githubからcloneしてライブラリ導入
- PHPからスプレッドシート追記 > 1. プロジェクト直下にダウンロードしたJsonファイルを設置
まで同じです!
Googleスプレッドシートの値を読み込む
サンプルソース
動作確認はしていないので、エラーになったらごめんなさい。
require_once __DIR__.'/vendor/autoload.php'; class GoogleSpreadSheetClass { protected $service; protected $serviceKeyJson = 'サービスアカウントキーが書いてあるファイル.json'; function __construct() { putenv('GOOGLE_APPLICATION_CREDENTIALS=' . dirname(__FILE__) . '/' . $this->serviceKeyJson); $client = new Google_Client(); $client->useApplicationDefaultCredentials(); $client->addScope(Google_Service_Sheets::SPREADSHEETS); $client->setApplicationName('test'); $this->service = new Google_Service_Sheets($client); } /** * @param string スプレッドシートID * @param string 最終列アルファベット * @param string 最終行番号 * @return boolean 成功 or 失敗 */ function main($spreadSheetId, $lastColumnNumber, $lastRowNumber) { $lastRange = $lastColumnNumber . $lastRowNumber; try { $data = $this->service->spreadsheets_values->get($spreadSheetId, 'A1:' . $lastRange); var_dump($data->values); } catch (Exception $e) { return false; } return true; } }サンプルスプレッドシート
解説
spreadsheets_values
の関数は、
- 第一引数にスプレッドシートのID
- 第二引数にスプレッドシートの範囲
を指定します。
スプレッドシートの範囲は、
string
で「A1:D6」などと指定します。
開始から終了まで斜めで囲む感じですね。返ってくる値
Array ( [0] => Array ( [0] => メーカー名 [1] => 弦の名前 [2] => おすすめジャンル [3] => 価格 ) [1] => Array ( [0] => Thomastik Infeld [1] => Spirocore Midium [2] => ジャズ、ポップス [3] => ¥18,900+tax ) [2] => Array ( [0] => Thomastik Infeld [1] => Spirocore Light [2] => ジャズ、ポップス [3] => ¥18,900+tax ) [3] => Array ( [0] => Pirastro [1] => Flexocor [2] => なんでも [3] => ¥28,980+tax ) [4] => Array ( [0] => Pirastro [1] => Original Flexocor [2] => オーケストラ [3] => ¥38,040+tax ) [5] => Array ( [0] => Pirastro [1] => The Jazzer [2] => ジャズ [3] => ¥28,440+tax ) )これを作った経緯
あるデータを突っ込んで、WordPressで固定ページ作りたいということがあったので、スプレッドシートの一行ずつで固定ページを作成するような処理を書いたときにやったやつです。
- 投稿日:2020-01-23T14:46:13+09:00
Laravel で バリデーションエラー時にJSで動的に増やしたinputの値を保持する
やりたいこと
jsで下記のようなinputをボタンを押したら追加していくというような可変な入力欄のとき
バリデーションエラーが起きたら、行が増えた状態で、それぞれに値が保持されていてほしいtest.blade.php<div> <input type="text" name="hoge[]"> </div>やりかた
Laravel 6.6で確認しています
バリデーションエラーが起こった時
old('hoge.0')
とやれば、一番初めの入力値がとれます。
つまり、indexを指定してあげればその値が、指定しなければ配列そのものが取れます。old('hoge.0'); old('hoge.1'); // hoge // hoge2 old('hoge'); // [0 => hoge, 1 => hoge2]なので、blade側でエラー時にループしてあげれば増えた状態で
入力値を保持できますtest.blade.php@empty(old('hoge')) <div> <input type="text" name="hoge[]"> </div> @else @foreach(old('hoge') as $value) <div> <input type="text" name="hoge[]" value="{{$value}}"> </div> @endforeach // ↑↓ どっちでも($loop->indexでも) @foreach(old('hoge') as $key => $value) <div> <input type="text" name="hoge[]" value="{{old('hoge.{$key}')}}"> </div> @endforeach @endempty
- 投稿日:2020-01-23T14:15:12+09:00
Laravelをデザインパターンで考察する ~ Builder Pattern ~
デザインパターン
デザインパターンは、いろんな文脈で使われるが、ここでのデザインパターンはGoFのデザインパターン。
その中で今回は、Builder Patternでの実装箇所を取り上げる。
Builder Patternは、デザインパターンの分類、生成・構成・振る舞いのうちの生成に分類される。
生成過程を抽象化したり、コンストラクタの引数が多いとか複雑なときに使うデザインパターン。基本的な説明は以下のリンクに任せる
[デザインパターン]
https://www.techscore.com/tech/DesignPattern/index.html/[Builderパターン | PHPデザインパターン]
https://www.ritolab.com/entry/125Illuminate\Support\Managerクラス
Illuminate\Support\Managerクラスは、Laravelに用意されているBuilder Patternを簡単に利用するための抽象クラス。
インスタンス生成過程を隠蔽し、シンプルに再利用するために継承して使用することができる。
Session, Cache, Auth等で使用されていて、Authの認証方式やSessionのストレージをLaravelで初期状態で使用できるもの以外を使いたい場合は、自前ですべて作成するのではなく、Manageクラスに従って拡張すれば、Laravelの機能をできるだけ残したまま拡張できる。
また、configの変更等最小のステップでほぼコードを書くことなく、設定を変更してSessionを別プロジェクトでも再利用が簡単にできる。
なので機能拡張の際、Managerクラスを継承あるいは引用しているクラスをLaravelが使用している場合、これに乗っかって拡張するのが楽。
新規機能を作る時も、Managerクラスを使えば、Driverを使ったBuilderパターンを容易に使用できる。最近はLaravelのManagerコンポーネント(AuthManager等)は、継承せずに実装されるように変更されている(処理自体は踏襲されている)。
現在継承して使用されているのはSessionManagerなのでSessionManagerを使用して説明する。SessionManager
Sessionで使用する機能(Illuminate\Session\Store)を再利用しつつ、使用者は保存先のストレージをconfigを使用して簡単に変更し、Session用のオブジェクト(Illuminate\Session\Store)を生成できるようにというのが、SessionManagerの趣旨
Sessionのストレージの選択肢は、ほぼ確定しているのでそのストレージごとに生成過程を見やすく管理できる。SessionServiceProviderh
Illuminate\Session\SessionServiceProviderprotected function registerSessionManager() { $this->app->singleton('session', function ($app) { return new SessionManager($app); }); }SessionServiceProviderでSessionManagerがコンテナに登録されている
SessionManagerは、あくまで生成過程を抽象化してくれているもので、最終的にはIlluminate\Session\Storeのインスタンスが帰ってくる。SessionManager
Illuminate\Session\SessionManagerprotected function callCustomCreator($driver) //たぶん独自にDriverを登録できるやつ { return $this->buildSession(parent::callCustomCreator($driver)); } // 既存のDriverメソッドの抜粋 protected function createFileDriver() { return $this->createNativeDriver(); } protected function createMemcachedDriver() { return $this->createCacheBased('memcached'); } protected function createDynamodbDriver() { return $this->createCacheBased('dynamodb'); } // ここで/config/session.phpのdriverをDriverとしてセットしている。 public function getDefaultDriver() { return $this->app['config']['session.driver']; }生成過程で必要になる要素を、Driverとして登録する。
ここではSessionを保存するためのStorageをDriverとして登録しておく。
既にFile、memcached、Redis、Dynamoとかいくつか用意されている。
callCustomCreatorで独自のDriverを登録できるはず(試したことない)
各Driverメソッド内で生成過程の差異を吸収する。
Sessionに別のストレージを使いたい場合この仕組みを利用するとLaravelの仕様に乗っかれて便利。また、configでSessionを設定できるように、$this->app['config']を使用してStoreの生成を抽象化している。
Manager
Illuminate\Support\Managerprotected function createDriver($driver) { if (isset($this->customCreators[$driver])) { return $this->callCustomCreator($driver); } else { $method = 'create'.Str::studly($driver).'Driver'; if (method_exists($this, $method)) { return $this->$method(); } } throw new InvalidArgumentException("Driver [$driver] not supported."); } // これでdriverメソッドで生成したStoreのメソッドをSessionManagerから直接使用できる // Session::get('hoge')て感じで(SessionはSessionManagerのファサード) public function __call($method, $parameters) { return $this->driver()->$method(...$parameters); }createDriverにはDefaultDriverが渡されて、
\$method = 'create'.Str::studly($driver).'Driver';
のところでDriverメソッドを確定している。SessionManagerの効果
SessionManagerにより、SessionのストレージをDATABASEにしたい場合、下記を設定するだけで、SessionファサードでDatabaseストレージに対応したStoreインスタンスが取得できるようになる。
.envSESSION_DRIVER=databaseArtisanコマンド実行php artisan session:table php artisan migrateSession処理Session::all(); Session::get('hoge');SMS用のManagerクラスを作ってみる
Smsは外部サービスに処理を投げるので、Driverとして使用するSmsサービスを定義しておく。
今回は、NexmoとTwilioのDriverメソッドを定義する。このメソッドからSms送信用のDriverクラスを定義しておく。なのでSmsManagerが返すインスタンスは、Driverクラス。App\Components\Sms\SmsManager
SmsManager<?php namespace App\Components\Sms; use Illuminate\Support\Manager; use Nexmo\Client as NexmoClient; // NexmoのPHPクライアント use Twilio\Rest\Client as TwilioClient; // TwilioののPHPクライアント use App\Components\Sms\Drivers\NullDriver; // configに設定がない時用のDriver use App\Components\Sms\Drivers\NexmoDriver; use App\Components\Sms\Drivers\TwilioDriver; use Nexmo\Client\Credentials\Basic as NexmoBasicCredentials; // Nexmoの認証に使うやつ class SmsManager extends Manager { public function channel($name = null) { return $this->driver($name); } public function createNexmoDriver() { return new NexmoDriver( $this->createNexmoClient(), $this->app['config']['sms.nexmo.from'] ); } public function createTwilioDriver() { return new TwilioDriver( $this->createTwilioClient(), $this->app['config']['sms.twilio.from'] ); } protected function createNexmoClient() { return new NexmoClient( new NexmoBasicCredentials( $this->app['config']['sms.nexmo.key'], $this->app['config']['sms.nexmo.secret'] ) ); } protected function createTwilioClient() { return new TwilioClient( $this->app['config']['sms.twilio.key'], $this->app['config']['sms.twilio.secret'] ); } public function createNullDriver() { return new NullDriver; } public function getDefaultDriver() { return $this->app['config']['sms.default'] ?? 'null'; } }createClientで、SMSのapiの初期化をして各DriverClassに渡す。
App\Components\Sms\Drivers\Driver
各外部サービスのDriverクラスの抽象クラス、共通の設定処理を抽象化しておく。
App\Components\Sms\Drivers\Driver<?php namespace App\Components\Sms\Drivers; use Illuminate\Support\Arr; use App\Components\Sms\Exceptions\SmsException; abstract class Driver implements { protected $recipient; protected $message; abstract public function send(); public function to(string $recipient) // 宛先の設定 { $this->recipient = $recipient; return $this; // メソッドチェーンしたいので$thisを返しておく。 } public function content(string $message) // メッセージの設定 { $this->message = $message; return $this; } }App\Components\Sms\Drivers\TwilioDriver
Twilio用のDriverクラス
App\Components\Sms\Drivers\TwilioDriver<?php namespace App\Components\Sms\Drivers; use Twilio\Rest\Client as TwilioClient; class TwilioDriver extends Driver { /** * The Twilio client. * * @var \Twilio\Rest\Client */ protected $client; /** * The phone number this sms should be sent from. * * @var string */ protected $from; /** * Create a new Twilio driver instance. * * @param \Twilio\Rest\Client $twilio * @param string $from * @return void */ public function __construct(TwilioClient $twilio, $from) { $this->client = $twilio; $this->from = $from; } public function send() // 送信処理 { return $this->client->messages->create( $this->recipient, [ 'from' => $this->from, 'body' => trim($this->message) ] ); } }使ってみる
SMS::channel('twilio') ->to($phoneNumber) ->content('Using twilio driver to send SMS') ->send();channelメソッドでDriver(TwilioかNexmo)を指定して、各種設定をしてメッセージを送信できて便利。
参考サイト
https://www.slideshare.net/BobbyBouwmann/laravel-design-patterns-86729254
https://itnext.io/building-driver-based-components-in-laravel-5b390dc25bd9
https://github.com/orobogenius/building-driver-based-components
- 投稿日:2020-01-23T13:48:25+09:00
Dockerで入れたPostgreSQLへのPDO接続でエラーが出て悩んだ。
出たエラー
こんにちは、Docker初心者です。
なんならMySQLばっかりだったのでPostgreSQLも初です(汗)タイトルのままです。
docker-compose.ymlで入れたPostgreSQLへのPDO接続をしようとしたら、
下のようなエラーが出てしまったンゴです。error.SQLSTATE[08006] [7] could not translate host name "postgres" to address: Name or service not knownちなみに書いていたPDOはこんな感じです。
property.phptry { $pdo_connect = new PDO( 'pgsql:host=postgres; dbname={name};', '{user}', '{dbname}' ); } catch(PDOException $e) { var_dump($e->getMessage()); }解決
問題はdocker-compose.ymlとPDOの両方にありました。ymlファイルはこちら。
コメントアウトで間違ってる点書いてます。docker-compose.ymlpostgres-db: #なんと、この postgres-db がHOST名になるらしい。知らなかった。PDOほhostを変える必要あり。 image: postgres:8.4 environment: POSTGRES_ROOT_PASSWORD: root POSTGRES_USER: user POSTGRES_PASSWORD: dbname POSTGRES_DATABASE: db #これ、本当は POSTGRES_DB: db と書かないといけない。 TZ: Asia/Tokyo hostname: postgres #HOSTがおかしいのかと思ってあとから付け加えたもの。いらない。 ports: - 6001:5432修正後
property.phptry { $pdo_connect = new PDO( 'pgsql:host=postgres-db; dbname={name};', '{user}', '{dbname}' ); } catch(PDOException $e) { var_dump($e->getMessage()); }docker-compose.ymlpostgres-db: image: postgres:8.4 environment: POSTGRES_ROOT_PASSWORD: root POSTGRES_USER: user POSTGRES_PASSWORD: dbname POSTGRES_DB: db TZ: Asia/Tokyo ports: - 6001:5432接続完了!
パイセンに感謝です。
- 投稿日:2020-01-23T10:42:16+09:00
グラフ描画のためのデータベース操作
グラフ描画のためのデータベース操作
どうやってデータを間引くか
剰余系を使って間引く
データベースに記録したデータをグラフにしたい場合、あまりにもデータが多いので間引く必要がある。
そもそも、データベースはスキップという操作があまり得意ではない。
それでもテーブルのIDを単純に剰余系で間引くSQLは、こんな感じ。SELECT id,`CreateAt`,`機器ID`,`温度` FROM data_table WHERE MOD(id,100)=0これだと、複数の機器がデータベースに記録されていた時に、データの最初と最後が欠けてしまう機器が出てくる。
そこで機器ごとの連続した番号が必要と思い、データベースに記録する時に連番を振っておくseq列を追加。SELECT id,`CreateAt`,`機器ID`,`温度` FROM data_table WHERE `機器ID`='XXXX-XXXX' AND MOD(seq,100)=0一応もれなく計算出来るが、機器毎にSQLを発行して読み出すので、機種が増える毎に計算時間が遅くなって行く。
データベースの得意なのは集計
ある時、別件で1時間毎にデータを集計する機能を追加して気づいた。
スキップするのではなく、1時間毎の平均を取るSQLを書いてみた。SELECT DATE_FORMAT(`CreateAt`, '%Y-%m-%d %H:00:00') AS hour, `機種ID`,AVG(`温度`) AS tempr FROM data_table GROUP BY `機種ID`,hour HAVING tempr別の書き方で、こう書ける事も分かった。
SELECT FROM_UNIXTIME(TRUNCATE(UNIX_TIMESTAMP(`CreateAt`)/3600,0)*3600) as sec, `機器ID`,AVG(`温度`) AS tempr FROM data_table GROUP BY `機種ID`,sec HAVING temprこの書き方だと任意の秒数で集計ができるのに気づいた。
グラフの横軸は時間なので、横軸に表示する範囲の日時を描画点数で割って、あらかじめ描画点の間隔の秒数を求めておく。$start = strtotime(最初のdatetime); $end = strtotime(最後のdatetime); $diff = $end - $start; $secPerPoint = intval($diff/描画点数);SELECT FROM_UNIXTIME(TRUNCATE(UNIX_TIMESTAMP(`CreateAt`)/{$secPerPoint},0)*{$secPerPoint}) as sec, `機器ID`,AVG(`温度`) AS tempr FROM data_table GROUP BY `機種ID`,sec HAVING tempr間引くよりも平均を取る。そうすればGROUP BYが使えるので、一度のSQLで機器単位の平均を求めることができる。プログラムも単純になる。
肝心のスピードだが、多少早くなったが、グラフに送るデータの量が多いところがボトルネックになってた。例えばX軸の時間とかは、全機種共通なので1つだけ送るとかデータ転送量減らす予定。