20200328のPHPに関する記事は10件です。

LaravelでDBのカラム(mysql)のコメントの作成・確認方法

はじめに

当記事は、Laravelのマイグレーションファイルでカラムにコメントをつける方法、およびmysqlでそのカラムにつけられているコメントを確認する方法を書いています。

経緯

先日、業務内でデータベースのカラムにコメントがつけられていないために、間違った設計を行ってしまったことがありました(自己責任です)。
やはり、テーブル名、カラム名から推測しにくかったり、外部にも同名のカラムがあって、且つ意味するものが違う場合などはカラムにコメントを付けたほうが良いと思います。

マイグレーションファイルの記述

二行目のcomment()でカラムにつけるコメントを記述する。

migration.php
Schema::create('users', function (Blueprint $table) {
    $table->string('name')->comment('名前');
});

MySQLでの確認方法

show full columns from users;

右端のcommentのところに名前と出てますね。

+-------------------+---------------------+--------------------+------+-----+---------+----------------+---------------------------------+---------+
| Field             | Type                | Collation          | Null | Key | Default | Extra          | Privileges                      | Comment |
+-------------------+---------------------+--------------------+------+-----+---------+----------------+---------------------------------+---------+
| name              | varchar(255)        | utf8mb4_unicode_ci | NO   |     | NULL    |                | select,insert,update,references | 名前    |

終わりに

カラムにコメントがあった方が、後々プロジェクトに参加してくるエンジニアさんにも親切ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

phpで、$a~$zの変数についてワンライナーで定義する

はじめに

このコードはネタコードです。
\$a ~ \$zの変数について、ワンライナーで定義したいと思います。格納する値は5です。

ワンライナー

$a = ( $b = ( $c = ( $d = ( $e = ( $f = ( $g = ( $h = ( $i = ( $j  = ( $k = ( $l = ( $m = ( $n = ( $o = ( $p = ( $q = ( $r = ( $s = ( $t = ( $u =  ( $v = ( $w = ( $x = ( $y = ( $z = 5 )))))))))))))))))))))))));

出力すると

$ php -a
Interactive shell

php > $a = ( $b = ( $c = ( $d = ( $e = ( $f = ( $g = ( $h = ( $i = ( $j  = ( $k = ( $l = ( $m = ( $n = ( $o = ( $p = ( $q = ( $r = ( $s = ( $t = ( $u =  ( $v = ( $w = ( $x = ( $y = ( $z = 5 )))))))))))))))))))))))));

何も出力されず終わります。

get_defined_vars()で中身を確認

\$a ~ \$zがちゃんと定義されているか、その中身をget_defined_vars()の出力で確認してみましょう。

php > $a = ( $b = ( $c = ( $d = ( $e = ( $f = ( $g = ( $h = ( $i = ( $j = ( $k = ( $l = ( $m = ( $n = ( $o = ( $p = ( $q = ( $r = ( $s = ( $t = ( $u =  ( $v = ( $w = ( $x = ( $y = ( $z = 5 )))))))))))))))))))))))));
php > var_dump(get_defined_vars());
array(33) {
  ["_GET"]=>
  array(0) {
  }
...
  ["a"]=>
  int(5)
  ["b"]=>
  int(5)
  ["c"]=>
  int(5)
  ["d"]=>
  int(5)
  ["e"]=>
  int(5)
  ["f"]=>
  int(5)
  ["g"]=>
  int(5)
  ["h"]=>
  int(5)
  ["i"]=>
  int(5)
  ["j"]=>
  int(5)
  ["k"]=>
  int(5)
  ["l"]=>
  int(5)
  ["m"]=>
  int(5)
  ["n"]=>
  int(5)
  ["o"]=>
  int(5)
  ["p"]=>
  int(5)
  ["q"]=>
  int(5)
  ["r"]=>
  int(5)
  ["s"]=>
  int(5)
  ["t"]=>
  int(5)
  ["u"]=>
  int(5)
  ["v"]=>
  int(5)
  ["w"]=>
  int(5)
  ["x"]=>
  int(5)
  ["y"]=>
  int(5)
  ["z"]=>
  int(5)
}

きちんと定義できていることが確認できました。

説明

phpの式について調べていたら、こんな記述を見つけました。

代入式は、それ自体、代入値を評価します。 この場合、その値は 5 になります。 このことは、実際には、$a = 5 は、それが何をするかによらず、 値 5 を有する式であることを意味します。 つまり、 $b = ($a = 5) のように書くことは、 $a = 5; $b = 5; と書くのと 同様なのです。
ref: https://www.php.net/manual/ja/language.expressions.php

phpにおける式の定義は、"値のあるもの全て"です。
代入式もこの定義に漏れず値を持っています。$a = 5;の代入式それ自体が持つ値は5であり、$b = ( $a = 5 );と記載した場合、それはすなわち$a = 5; $b = 5;と同意となります。

ということで、それを\$a ~ \$zまで入れ子にして繰り返したのが、本ワンライナーでした。

追記

この長い式も、値を持っています。
再帰的に処理され、最終的には$a = 5という式になります。

php > echo $a = ( $b = ( $c = ( $d = ( $e = ( $f = ( $g = ( $h = ( $i = ( $j  = ( $k = ( $l = ( $m = ( $n = ( $o = ( $p = ( $q = ( $r = ( $s = ( $t = ( $u =  ( $v = ( $w = ( $x = ( $y = ( $z = 5 )))))))))))))))))))))))));
5

"echo"で式自体の値を吐き出させると、5が出力されます。

おわりに

本ワンライナーは思いつきで書いたネタコードです。
プロダクトのコードではこんなことはせずに配列を使いましょう。

追記

よければコメント欄もご覧ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

herokuとPHPでオウム返しlinebotを作る(Windows10)

目的

目的は,LINEでオウム返しをするBOTを作ること.
※当方はPHPを含め初心者であるため,内容についてのミス等あればぜひご指摘頂きたいです.

作成したBOTとリポジトリ

こちらから友達追加ができます.
https://lin.ee/eNVEud5
M.png  友だち追加
Githubのリポジトリ:https://github.com/rintaro9147/linebot20200328

準備

  1. herokuの登録!
  2. LINE BOTの登録!プロバイダーを作成し,Messaging APIのチャネルを作る.詳しくは後述.
  3. phpとcomposerをインストール!(このへんは省略)
  4. 正常にインストールできたか,バージョンの確認をする.
heroku -v
php -v
composer -v

composerは,ヘルプとともにでかでかとCOMPOSERと表示されます.

また,gitのアカウントは登録しているものとして進めます.

オウム返しのphpの準備

アカウントを登録後,ubuntsuでログインします.コマンドを打つとブラウザに遷移するので,ログインボタンをポチる.

$ heroku login

次にherokuのアプリケーションを作ります.適当なアプリ名をつけてください.使えない場合,エラーとなるので,ほかのものを試してください.

heroku create <アプリ名>
Creating  <アプリ名>... done
https://<アプリ名>.herokuapp.com/ | https://git.heroku.com/<アプリ名>.git

以下のコマンドを順に打ってください.フォルダ,phpファイルを作成して,とりあえずデプロイしておきます.

$ mkdir linebot
$ cd linebot
$ touch index.php
$ git init
$ git commit -m "make it better"
$ git push heroku master

続いて,composerでLINE-BOT-SDKを導入していきます.

$ composer require linecorp/line-bot-sdk

これでlsコマンドを打つと,中にvenderファイルなどができると思います.

次はLINE BOTの設定です!こっちはそこまで難しくありません!

LINE BOTの設定

ここからコンソール画面にアクセスし,作ったプロバイダとチャネルをクリックしていくと,この画面にたどり着きます.

home.PNG

チャネルシークレットの発行

その画面の下のほうに,チャネルシークレットとあるので,横の発行ボタンを押して,テキストファイルか何かにコピペして控えておきましょう.

channel_secret.PNG

アクセストークンの発行

次に,「Messaging API設定」タブに移動し,一番下のチャネルアクセストークンを発行します.同様に控えましょう.

channel_acces_token.PNG

アカウント機能の編集

次に,すぐ上のアカウント機能の編集を行います.下2つのどちらかの編集をクリックします.

kinou.PNG

そうしたら,以下のようにしてください.

kinou2.PNG

Webhookの設定

最後にすぐうえにあるwebhook設定です.https://<アプリ名>.herokuapp.com/index.phpと入力します.index.phpの部分はなくても良し.webhookの利用をONにして,検証ボタンを押してください.成功と表示されたらOKです.

webhook.PNG

そのページにあるQRで自分のスマホで友達追加をしておきましょう.
LINE側の設定は以上です.

phpファイルの記述

index.php
<?php
require_once __DIR__ . '/vendor/autoload.php';

$httpClient = new \LINE\LINEBot\HTTPClient\CurlHTTPClient(getenv('CHANNEL_ACCESS_TOKEN'));
$bot = new \LINE\LINEBot($httpClient, ['channelSecret' => getenv('CHANNEL_SECRET')]);
$sign = $_SERVER["HTTP_" . \LINE\LINEBot\Constant\HTTPHeader::LINE_SIGNATURE];
$events = $bot->parseEventRequest(file_get_contents('php://input'), $sign);

foreach ($events as $event) {
    if (!($event instanceof \LINE\LINEBot\Event\MessageEvent) ||
        !($event instanceof \LINE\LINEBot\Event\MessageEvent\TextMessage)) {
        continue;
    }
    $bot->replyText($event->getReplyToken(), $event->getText());
}

記述したら,前と同じ方法でデプロイしましょう.

環境変数の設定

最後に控えておいたアクセストークンとアクセスシークレットの設定です.これをやらないとherokuとLINEがやり取りできません.cmdを開いて,以下のコマンドを入力してください.

$heroku config:set CHANNEL_ACCESS_TOKEN="控えたアクセストークンをコピペ" -a <アプリ名>
$heroku config:set CHANNEL_SECRET="控えたチャネルシークレット" -a <アプリ名>
$heroku config

最後のコマンドで正しく入力されたか確認してください.

参考にした記事

 参考にしたQiita記事をここに記します.感謝いたします.

  1. Herokuにデプロイするまでに使うコマンド
  2. git push heroku master がエラーを吐く
  3. PHPで作成したLINEBOTをHerokuで動かしてみた

結果

目的通り,オウム返しするbotを作ることができました!

考察

  • 最初だけオウム返しが遅い → 解明中
  • git add .をしたとき,warning: LF will be replaced by CRLF inがたくさん出た.→ここで解決した.ありがとうございます.
  • 先にindex.phpの中身を書いてデプロイすると,webhookの検証でエラーがでる.→空もしくはhello worldくらいのphpファイルを先にデプロイして検証すると,上手くいった.そのあとにindex.phpの中身を書く.先に中身を書いてしまうことが問題なのではなく,今回たまたまであって,ほかに要因がありそう.

最後に

初めてLINE BOTを作成したが,なんだかんだ作ることができてよかったです.また,Qiita記事も初めての投稿なので,また一つ経験ができたので,今後も書いていきたいと思います.重ねて記事に不備等があれば,編集リクエストお願いします.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Composerをインストールする

composerをダウンロードします

https://getcomposer.org/composer-stable.phar

特定のバージョンをダウンロードしたい場合は下記からダウンロードします
https://getcomposer.org/download#composer-history-caption

composer.bat作成

ダウンロードしたcomposer.pharと同じフォルダ内にcomposer.batを作成する

composer.bat
@php "%~dp0composer.phar" %*

ファイル構成は下記となります

`-- composer
    |-- composer.bat
    `-- composer.phar

環境変数修正

PATHという名前の環境変数の値にcomposer.bat格納フォルダのパスを追記する

確認

コマンドラインでcomposer -Vと入力する

Composer version x.x.x xxxx-xx-xx xx:xx:xx
と表示されることを確認する

完了

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PDOの静的プレースホルダで整数をバインドするとけったいな結果になるケース

PHPの提供するデータベースアクセスライブラリPDOの挙動について調べていて、動的プレースホルダ(Prepared Statementのエミュレーション)の場合については以下の記事にまとめました。

PHP 7.2以降におけるPDO::PARAM_INTの仕様変更

では静的プレースホルダ(Prepared Statement)の場合はどうかというと、中々にカオスな状況になっておりまして、まだまとめられていませんが、特徴的なトピックスについてこちらに書いてみます。

PoCを以下に示します。

<?php
  $db = new PDO("mysql:host=HOST;dbname=DBNAME;charset=utf8", USER, PASSWORD);
  $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  // 静的プレースホルダ
  $ps = $db->prepare("SELECT :num");
  $ps->bindValue(':num', 2147483648, PDO::PARAM_INT);
  $ps->execute();
  $row = $ps->fetch(PDO::FETCH_NUM);
  echo "result = ${row[0]}\n";

単にプレースホルダに2147483648をバインドして、それをSELECTして表示しているだけです。
これをCentOS6, CentOS7, CentOS8のそれぞれ(32ビットが版が存在するCentOS6は、32ビットと64ビットの両方)で動かしてみた結果は以下のとおりです。なお、PHPはCentOSの標準パッケージのものです。

CentOS6 (32ビット)、CentOS8: 2147483648
CentOS6 (64ビット)、CentOS7: -2147483648

なぜそうなるかは調べてあるのですが、まずはクイズとして出題してみます。分かる人、いますか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】PHPマスター講座 vol.1(文字列を学ぼう)

みなさんこんにちは!けいです

今日から復習の意味も込めて
Qiitaでの投稿を始めていきます。

今後のQiitaでの投稿は可能な限り

【プログラミング初心者でも迷わずに理解できる構成】でまとめていきます。

もし記事内容に誤り及び不明点があれば都度コメントでいただければと思います!

それでは早速、本題にいきましょう。

【PHPマスター講座】文字列とは

====================================

▶︎Print構文を一言で表すと??

print(プリント)は印刷するという意味の英語で

ようは

【文字列をサイト上で表示(印刷)するよ!】

という(直感でわかりやすい)意味です。

Print構文の書き方はコチラ↓↓

print 文字列

print自体は関数ではないので
引数の文字列を括弧()で囲む必要はありません。

====================================

▶︎Print構文って、どうやって使うの??

?ポイント?

・文字列の中で「'」を使いたい場合
▶︎文字列全体を「"」で囲む

・文字列の中で「'」「"」の両方を使いたい場合
▶︎「"」の前に【\】を必ず記載する

・「.(ドット) '追記したい文字'」を使うことで
▶︎「文字列連結(つなげて文字をかける)」が可能
▶︎ドットの前後は「半角スペースで空欄」を作ると確認しやすい

・printの中の最後に「"\n」を記載することで
▶︎「改行」した状態で表示することが可能

まずは説明をする前に
実際に例文を一つ見ていきましょう。

たとえばあなたが

「わたしはPHPを勉強しています」と英語が書く場合は

print("I'm studying \"PHP\".");

上記のように書くと

【表示結果】
I'm studying "PHP".

このようにサイト上で
文字列を表示(印刷)することができます♪

たとえば

print("I'm studying \"PHP\".");
print("I'm studying \"Python\".");

上記のような形で
2つ連続した文字列を書く場合

サイト上でどのように表示されるのかと言うと・・・

【表示結果】
I'm studying "PHP".I'm studying "Python".

上記のように「改行されないまま文字列が連続で表示されてしまう」ので、対策として「\n(エスケープシーケンス)」を足します。

print("I'm studying \"PHP\". " . "\n");
print("I'm studying \"Python\". " . "\n");

【表示結果】
I'm studying "PHP".
I'm studying "Python".

さっきの改行なしと比べると
かなりスッキリしますよね!

?ポイント?

・文字列の中で「'」を使いたい場合
▶︎文字列全体を「"」で囲む

・文字列の中で「'」「"」の両方を使いたい場合
▶︎「"」の前に【\】を必ず記載する

・「.(ドット) '追記したい文字'」を使うことで
▶︎「文字列連結(つなげて文字をかける)」が可能
▶︎ドットの前後は「半角スペースで空欄」を作ると確認しやすい

・printの中の最後に「"\n」を記載することで
▶︎「改行」した状態で表示することが可能

こんな感じですね?

====================================

▶︎echoとprintとの「違い」は??

echoでもprintでも

「文字列」を出力することができますが…

先に結論からお伝えすると

主な違いは「下記の3つ」です。


①echoは戻り値を持たない
(printの戻り値は常に1)

②echoは複数の文字列を出力できる
(printは1つしかできない)

③echoは引数を複数セット可能
(printで指定できる引数は1つだけ)


一つずつ順番に解説していきます。

①echoは戻り値を持たない(printの戻り値は常に「1」)


・echo は文(statement)なので
【他の式や文の一部として使うことができない。】
+(戻り値を持たない)

・print は式(expression)なので
【他の式や文の一部として使うことができる。】
+(戻り値を常に1)


上記のようになります。

具体例としては下記です。

・一つ目(echo)
$greeting = echo "Hi Nice to meet you!";

・二つ目(print)
$greeting = print "Hi Nice to meet you!";

一つ目(echo)は戻り値がないので
▶︎「エラーが出力」されます。

二つ目(print)は、戻り値があるので
▶︎「正常に出力」されます。

②echoは複数の文字列を出力できる(printは1つしか不可)

こちらはタイトル通りの意味ですが
ざっくりとした構文イメージも載せておきますね。

・構文イメージ(echoの場合)
echo(string 文字列1 [, string 文字列n ... ] )

・構文イメージ(printの場合)
print(string 文字列 )

ようは

・【echo】文字列を複数セットできるか
・【print】文字列を一つしかセットできない

この違いしかないので、想像よりもだいぶシンプルです!!

③echoは引数を複数セット可能(printで指定できる引数は1つだけ)

・一つ目(echo)
echo "Hello", "My name is ○○";

・二つ目(print)
print "Hello", "My name is ○○";

一つ目(echo)は、引数を複数セットできるので
▶︎「正常に出力」されます。

二つ目(print)は、引数を一つしかセットできないので
▶︎「エラーが出力」されます。

上記のとおりです。

ただし、echoではカンマ「,」区切りで複数の文字列を指定できるのに対し、printではそれができません。

※echoで括弧「()」をつけた場合、カンマ「,」区切りでの複数指定はできず、かつ括弧「()」で括って複数指定する場合、エラーが出るので注意して下さい。

====================================

さて、今回のPHPマスター講座 vol1(文字列を学ぼう)はここまでとなります!

少しでもあなたのPHP学習のお役に立てれば幸いです?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ECCUBE4】登録メンバーごとに商品・受注を管理できるようにする①

目的

ログインメンバーごとに
・商品管理
・受注管理
できるようにカスタマイズする。


方針

メンバーに割り当てられているcreator_idを活用します。


今回すること

・メンバーが登録した商品しか管理画面商品一覧表示されるようにします。
・異なるメンバーが登録した商品は同じカートに入れられないようにします。


メンバーが登録した商品しか管理画面商品一覧表示されるようにします

こちらをそのまま記載します。
https://a-zumi.net/eccube4-admin-product-list-by-menber-customizer/


異なるメンバーが登録した商品は同じカートに入れられないようにする

受注を管理するため、同じメンバーの商品しかカートに入れられないようにします。

app/Customize/Service/Cart/ProductCreatorCartAllocator.php
<?php
namespace Customize\Service\Cart;

use Eccube\Entity\CartItem;

/**
 * 販売種別と予約商品フラグごとにカートを振り分ける CartItemAllocator
 */
class ProductCreatorCartAllocator implements \Eccube\Service\Cart\CartItemAllocator
{
    /**
     * 商品の振り分け先となるカートの識別子を決定します。
     *
     * @param CartItem $Item カート商品
     * @return string
     */
    public function allocate(CartItem $Item)
    {
        $ProductClass = $Item->getProductClass();
        if ($ProductClass && $ProductClass->getSaleType()) {
            $salesTypeId = (string) $ProductClass->getSaleType()->getId();
            return $salesTypeId . ':' . $ProductClass->getProduct()->getCreator()->getId();
        }
        throw new \InvalidArgumentException('ProductClass/SaleType not found');
    }
}
app/config/eccube/packages/cart.yaml
services:
    Eccube\Service\Cart\CartItemAllocator:
        class: Customize\Service\Cart\ProductCreatorCartAllocator
src/Eccube/Resource/locale/messages.ja.yaml
-front.cart.divide_cart: 同時購入できない商品がカートに含まれています。
+front.cart.divide_cart: 作者ごとにお買い上げが必要です。

宣伝

ハンドメイド猫グッズ専門ショップを運営しています。
ネコ好きの方、親しい間柄にネコ好きがいらっしゃる方、ぜひご覧ください。
■キャットギフト
https://cat-gift.net/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでポートフォリオ作成:カレンダー

記事の概要

作った背景、工夫したところ、機能をまとめました。

下にコードを記載しました。

背景

一般的なカレンダー(家の壁にかかっているものを想像してほしいです)を作成したときに、自分のスケジュールを書き込めると、カレンダーとしての価値がアップするのでないかなと思い作成にいたりました。

エクセルのようなレイアウトで日付とスケジュールを同時に表示するようなカレンダーを作成します。

工夫としては、当たり前ですが見た目を分かりやすく、使用者が使いやすいようにシンプルにしました。

目的

・PHPを用いて簡単なアプリケーションを作る
・見やすくて使いやすいアプリを作る
・カレンダー機能+スケジュール機能で単純なカレンダーに価値をつける

スペック

言語
PHP 7.4.2
データーベース
MariaDB 10.4.11
開発環境
Apache 2.4.41

ビジュアルスタジオコード使用

主な機能

・今月の日付の表示機能
・来月、前月のカレンダーへのリンク
リンクをクリックすると、来月もしくわ前月のカレンダーに飛びます

・スケジュールの表示機能
カレンダーの日付の横にスケジュールの時間と内容を表示する

・スケジュールの保存機能
日付、時間、スケジュールという情報をデータベースに保存する

・スケジュールの更新機能
スケジュールを更新する

・スケジュールの削除機能
スケジュールを削除する

※データベースのカラムの情報があると説明しやすいので、詳しくは下部で説明します。

開発手順

1、要件定義
2、環境選定
3、データベース設計
4、設計
5、コーディング

1、要件定義

今回のアプリでの主要な機能は
・今月の日付の表示
・スケジュールの表示
・スケジュールの保存、更新、削除

今月の日付が表示できるということは、月(month)の部分を変更することで来月、前月のカレンダーが表示可能になります。
ということは、すべての日付のカレンダーが作成可能になります。

スケジュールの保存には、データベースが必要です。

環境選定

PHP,MySQL,apacheを使用。

Windowsローカル上に環境を構築しました。

データベース設計

記入例

id day time   message  
5 2018-12-15 18:00 予定を書く

スケジュールの情報をデータベースに保存。
スケジュールの情報としては
id,day,time,messageとして
idにはオートインクリメントを設定して、idの値がかぶることはないようにしました。
dayには2020-03-27の形式で年月日を保存
timeにはtextタイプなので時間の表現の仕方は自由です
messageにはスケジュールの内容を保存します

設計

のちほど追加予定

 5.コーディング

設計を書いたのですが、後々変更することは多かったです。
理由は、コーディングしていて「このように表示する方が使用者にとっては、使いやすいのではないか」という考えの結果、レイアウトの変更をしました。

5.1データベース作成

ローカル環境のMysqlサーバーに
データーベースを作成。テーブルを作成。

5.2データベース接続確認

PDOを用いて、データーベースの接続。
try~catchを用いて、成功しているか判断

5.3今月の日付の表示機能

GIF 2020-03-28 8-39-33.gif

urlに?t=2020-04のような表記があれば、その年月(この場合2020-03)で取得し、表記がなければ今月の年月を取得する
これで、年月日の年月の情報を取得したので、年月の情報からDatePeriodを使って、年月日における日の部分の情報を取得します。
これで年月日の情報がそろったのでエクセルや表のような形式で表現します

5.4・来月、前月のカレンダーへのリンク

リンクをクリックすると、来月もしくわ前月のカレンダーに飛びます

GIF 2020-03-28 8-42-58.gif

5.3にて年月日の情報を取得しています。なのでリンクをその月(month)に+1すれば翌月、-1すれば前月へ飛ぶように設定しました。

5.5スケジュールの表示機能

カレンダーの日付の横にスケジュールの時間と内容を表示する

if文で取得している年月日とデータベースにあるdayの部分で一致するレコード(行部分)が存在したら、そのレコードのスケジュール時間と内容を日付の横に表示する

5.6スケジュールの保存機能

日付、時間、スケジュールという情報をデータベースに保存する
この時、日付の形式が2020-03-27のような形式でない場合はerrorとし
errorが存在する場合はエラーメッセージを表示する

GIF 2020-03-28 8-48-45.gif

5.7スケジュールの更新機能

スケジュールを更新する

GIF 2020-03-28 8-53-15.gif

構造上、日付1つに対して1つのメッセージになるため、
更新機能を使う場合、既存のスケジュールの情報を削除してから、また新しくスケジュールをデータベースに保存する方法を使用

※1つの日付に対して、複数のメッセージがある場合はidが新しいメッセージが表示される。
※また、ある日付に対してメッセージが存在するにも関わらず、メッセージを作成した場合は、メッセージは既存のメッセージが表示されるので、新しく作ったmemberテーブルは表示されない

5.8スケジュールの削除機能

スケジュールを削除する

削除ボタンを作って、削除を実行する
この時、日付に該当する箇所を変更した場合、その日付のメッセージが削除される。

5.9レイアウト

新しくスケジュールを作る機能はアイコンにして、右上に固定しました。
これは、下にスクロールしたとしても、スケジュール作成できるためです
配色の統一、シンプルにわかりやすいをこころがけました

課題点

・「1つの日付に対して、1つのスケジュール」でなく複数のスケジュールを表示すべきだが、技術的にできなかった。
・わかりやすく,シンプルに作ったつもりであるが、その分、説明不足で慣れれ宇までに時間がかかる可能性がある
・スケジュールの保存に関して、使用者の制約があり親切でない、
1999-03-20のような形式でなくタイプをradio形式(複数の選択肢から1つを選択する形式)で年、月、日に分けて表現すれば良かったかもしれない。
・スケジュールの削除に関して、入力されている年月日のデータの削除があることを説明していない
・オブジェクト思考がなってない。機能を別ファイルでプログラミングして、呼び出すことができてない

code

index.php
画像

index.php
<?php 
require('dbconnect.php');

//セキュリティ対策
function h($s){
  return htmlspecialchars($s,ENT_QUOTES,'UTF-8');
}
//

//今月を取得する
try{
if(!isset($_GET['t']) || !preg_match('/\A\d{4}-\d{2}\Z/',$_GET['t'])){
  throw new Exception();
}
  $thisMonth=new DateTime($_GET['t']);
}catch(Exception $e){
  $thisMonth = new DateTime('first day of this month');
}


//今月から来月と翌月を取得する
  $dt= clone $thisMonth;
  $prev = $dt->modify('-1 month')->format('Y-m');
  $dt= clone $thisMonth;
  $next=$dt->modify('+1 month')->format('Y-m');
//


$yearMonth=$thisMonth->format('F Y');

//日付、メッセージの表示機能
  $period=new DatePeriod(
    new DateTime('first day of'.$yearMonth),
    new DateInterval('P1D'),
    new DateTime('first day of'.$yearMonth.'+1 month')
  );

  //複数のメッセージをつけたいがつけれなかった
  $body='';
  $today=new DateTime('today');
  foreach($period as $day){

    $messages=$db->prepare('SELECT * FROM posts WHERE day=?');
    $messages->execute(array($day->format('Y-m-d')));
    $message=$messages->fetch();

    $todayClass=($day->format('Y-m-d') === $today->format('Y-m-d'))?'today':'';
    $body .= sprintf('<tr><td class="youbi_%d %s">%d</td><td>%s</td><td class="message"><a   href="message.php?t=%d-%02d-%2d">%s</a></td></tr>',$day->format('w'),$todayClass,$day->format('j'),$message['time'],$day->format('Y'),$day->format('n'),$day->format('d'),$message['message']);
  }
//




//cssでのレイアウト
//オブジェクト思考


//終了。前月次月のリンク.todayのリンク,データベースに保存する
//今日を太文字、土日は色変える,時間の指定もあり、消去、変更

?>



<!DOCTYPE html>
<html lang='ja'>
<head>
  <meta charset='utf-8'>
  <title>カレンダー</title> 
  <link rel='stylesheet' href='styles.css'>
</head>
  <body>
      <a class='write' href="message.php" name='write_message'><img  src="write.png"  alt="写真"></a>
    <table>
      <thead>
        <tr>
          <th><a href="/test/index.php?t=<?php echo h($prev); ?>">&laquo;</a></th>
          <th><?php echo $yearMonth; ?></th>
          <th><a href="/test/index.php?t=<?php echo h($next); ?>">&raquo;</a></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>day</td>
          <td>time</td>
          <td>message</td>
        </tr>   
        <?php echo $body;?>
      </tbody>
    </table>
  </body>
</html>

message.php
画像

message.php
<?php
require('dbconnect.php');


//入力機能は取得して、それをインサートするだけ

if(!empty($_POST)){
    //すでにメッセージが存在する場合、そのメッセージを消去する
    //先に消去をおした場合にはメッセージを消去してexitする
    if($_POST['delete'] ==='1'){
      $deletes=$db->prepare('DELETE FROM posts WHERE day=? ');
      $deletes->execute(array($_POST['day']));
      header('Location:index.php');
      exit();
    }


    if(!empty($_POST)){

      $posts=$db->prepare('SELECT * FROM posts WHERE day=?');
      $posts->execute(array($_POST['day']));
      $post=$posts->fetch();

      if(!empty($post['message'])){
      $deletes=$db->prepare('DELETE FROM posts WHERE day=? ');
      $deletes->execute(array($_POST['day']));
    }


  //日付の設定はシビアで形式が決まっているので、形式が違えばエラーをだす
  if( preg_match('/\A\d{4}-\d{2}-\d{2}\z/',$_POST['day'])){
    $message=$db->prepare('INSERT INTO posts SET day=? ,message=?,time=?');
    $message->execute(array(
      $_POST['day'],
      $_POST['message'],
      $_POST['time']
    ));

    header('Location:index.php');
    exit();
  }else{
    $error['day']='format';
  }

}


}
//messageの再取得、
  $messages=$db->prepare('SELECT * FROM posts WHERE day=?');
  $messages->execute(array($_REQUEST['t']));
  $message=$messages->fetch();

?>
<!DOCTYPE html>
<html lang='ja'>
<head>
  <meta charset='utf-8'>
  <title>カレンダー</title> 
  <link rel='stylesheet' href='styles.css'>
</
head>
  <body>
    <div class='title'>
      <a href="index.php"><< ホーム</a>
    </div>
    <div class='content'>

      <form action="" method='post'>
        <p class='delete_p'>メッセージを消去>><button type='subimt' class='delete'  name='delete' value='1'>消去</button></p>
        <p >日付 (※2020-01-01の形式で入力してください)</p>
        <input name='day' type="text" value="<?php echo $_REQUEST['t'];?>" >
        <?php if($error['day'] === 'format'): ?>
          <p class='error'>※2020-01-01の形式で入力してください。</p>
          <?php endif;?>
          <p>時間</p> 
          <input name='time' type="text" value='<?php echo $message['time'] ?>'>
          <p>メッセージ</p>

          <textarea name="message" id="" cols="30" rows="10" ><?php echo $message['message']; ?></textarea>
          <button type='submit'>メッセージを保存する</button>




    </div>
  </form> 

  </body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google reCAPTCHAを設置する

個人的な備忘録として

APIの取得

ここからいろいろ設定
Google reCAPTCHA

フロントサイド

recaptcha.html
<html>
<head>
<title></title>
<script src='https://www.google.com/recaptcha/api.js'></script>
</head>
<form action="" method="post">
    <div class="g-recaptcha" data-sitekey="サイトキーを入力"></div>
</form>
</html>

サーバサイド

Ninshou.php
    public function login() {
        if ($this->request->is('post')) {
            //RECAPTCHAからのResponseデータを受ける
            $recaptchaResponse = $_POST['g-recaptcha-response'];
            $secretKey = "セキュリティーキーを入力";

            //reCAPTCHA
            $response=file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret={$secretKey}&response={$recaptchaResponse}");

            $responseKeys = json_decode($response,true);
            if(intval($responseKeys["success"]) !== 1) {
                $this->Session->setFlash('「私はロボットではありません」にチェックを入れてください。');
            } else {
                if ($this->Auth->login()) {
                    $this->Session->setFlash('ログインに成功しました。');
                } else {
                    $this->Session->setFlash('ユーザー名かパスワードを確認してください。');
                }
            }

        }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サービスコンテナをつついて標準Facadeを差し替えたら思わぬところでドハマリした話

TL;DR

  • サービスコンテナをつついて、標準 Facade の機能を変更する実例です。
  • Facadeはサービスコンテナにアクセスするのでサービスコンテナのオブジェクトを差し替えればファサードも差し替わる。
  • ただしFacade は独自にキャッシュするのでタイミングによってはそれをクリアする必要がある。

なにがしたい?

サービスコンテナの解説記事で「標準機能をそっくり置き換えできる」と書いたことがあって、「ヘルパ関数の置き換え」は記事にしていましたが、今回はその Facade 編です。

テーマは 「テストのときだけトランザクションを実行しないDBファサード」

ことの発端は、テストで使っているトレイト DatabaseTransactions と、テスト対象内の DB::beginTransaction()DB::commit() が衝突を起こしてうまくテストが実行できなかったこと。それ自体は未だ原因がわかっていないのですが諦めて(笑)「いいや、Facadeだし、テスト中だけ Transaction をスルーするオブジェクト作って差し替えよう」と考えた次第です。

ちなみにファサードではなくヘルパ関数 db()->beginTransaction() だったり、DBオブジェクトを受け取って操作 $db->beginTransaction() だったりしても同様に差し替えできます。
が、ファサードに限って、さらにひと工夫必要だという話。

結論

ポイントは「※」のところにある キャッシュクリア です。
これは Facade だけに必要なお作法。

tests/SampleTest.php
use Illuminate\Support\Facades\Facade;
use TestCase;

class SampleTest extends TestCase
{
    use \Illuminate\Foundation\Testing\DatabaseTransactions;

    public function setup()
    {
        parent::setup();

        // サービスコンテナに入っている DatabaseManager を取得する
        $this->defaultDb = $this->app->make('db');

        // 別の DatabaseManager をツッコむ
        $mockDb = new DBConnectionWithoutTransaction($this->defaultDb);
        $this->app->instance('db', $mockDb);

        // Facade 内にキャッシュされたインスタンスを消しておく ※
        // 引数は ↑ のサービスコンテナのラベルと同じ
        Facade::clearResolvedInstance('db');
    }

    public function tearDown()
    {
        // 保存しておいたデフォルトに戻す
        $this->app->instance('db', $this->defaultDb);

        // Facade 内にキャッシュされたインスタンスを消しておく ※
        Facade::clearResolvedInstance('db');

        parent::tearDown();
    }

以下蛇足。

  • Facade::clearResolvedInstanceDB::clearResolvedInstance に置き換え可能です。こうしたほうが余計な use ...\Facade; を追加する手間が省けますが、clearResolvedInstance しなきゃいけないのは DB の機能要件ではなく Facade の特性なので、Facadeと明記して実行すべきかなぁと思ってそうしました。
  • $this->defaultDb は protected で宣言して保護すべきですが、テストクラスなので外からのアクセスとか考えなくていいかなぁと思って省きました。サンプルコードだし。納品するコードにはちゃんと書きますよ!(汗

解説

サービスコンテナに入っているオブジェクトを置き換える

Facade を作る記事でもチラッと書きました が、Facadeの中身はサービスコンテナに納められているオブジェクトなので、そのサービスコンテナのオブジェクトを置き換えると機能の差し替えができます。

よく使う置き換えのパターンは、代替オブジェクトを作って、instance メソッドで渡す方法。

$mockDb = new DBConnectionWithoutTransaction($this->defaultDb);
$this->app->instance('db', $mockDb);

configヘルパを置き換えたとき も同じ方法。

app/Providers/AppServiceProvider.php
$this->app->instance('config', new Repository($this->app->make('config')->all()));

その他の差し替え方法について詳しくは「サービスコンテナ講座 第3回 結合編」に書いていますが、今回のケースでは他の方法はあまり使わないかな。

代替オブジェクトをうまいこと作る方法は、経験と勘(笑)。
今回作ったものは、記事後方で解説しています。

サービスコンテナのラベルを調べる

さらっと 'db' というキーワードが出てきましたが、これはサービスコンテナに入っているDBサービスオブジェクトのラベルです(勝手にラベルと呼んでいますが正式名称は「abstract 抽象」)。これを調べるには Facade のソースファイルを開いて、getFacadeAccessor メソッドを確認します。

vendor\laravel\framework\src\Illuminate\Support\Facades\DB.php
namespace Illuminate\Support\Facades;

class DB extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'db'; // ← これ
    }
}

getFacadeAccessor が返しているのは、そのまんま、サービスコンテナのラベルです。
実際にアプリケーションの適当な場所でこのようにすると

$db = app('db');

データベースオブジェクトのインスタンスが得られるので、ファサードのメソッドを全く同じように使うことができます。

$db->beginTransaction();
$db->getQueryLog();

置き換えるタイミングと注意点

さて話を戻しまして、先程の「サービスコンテナのインスタンスを置き換えるコード」はどこに書くべきでしょうか。

$this->app->instance('db', $mockDb);

アプリケーション全体で差し替えた機能を使いたい場合は、最も良いのは、AppServiceProvider.phpregister() メソッドです。ただしこの時点では他のサービスクラスは初期化(実体化)されていないので、今回のケースのように「すでに初期化されたサービス」を使うには、boot() メソッドに書く必要があります(registerでも動くことがあります。registerよりも前に初期化されるサービスもあるので)。

でも今回はさらに用途が限定されています。
テストの特定メソッドの実行中だけ 差し替えたい。

そのまま考えると、そのテストの実行の直前と直後、setup()メソッドで良い気がします。

tests/SampleTest.php
    public function setup()
    {
        parent::setup();

        $this->defaultDb = $this->app->make('db');
        $mockDb = new DBConnectionWithoutTransaction($this->defaultDb);
        $this->app->instance('db', $mockDb); // ここ
    }

    public function tearDown()
    {
        $this->app->instance('db', $this->defaultDb);

        parent::tearDown();
    }

はい。問題ありません。ほとんどのケースではこれで動きます。

ただ、僕はこれをやって、コケました。壮大にドハマリしました。

問題は、Facadeだけの特別な性質にあります。

それは「Facade で使うインスタンスは、すべて自動的にシングルトンになる」という機能。
つまり内部キャッシュです。

Facade は初回実行時にインスタンスをキャッシュする

わかりやすく書くとこのようなイメージです。

$defaultDb = $this->app->make('db'); // デフォルトインスタンス
$mockDb = new DBConnectionWithoutTransaction($defaultDb); // 代替インスタンス
$this->app->instance('db', $mockDb); // 差し替える

DB::beginTransaction(); // $mockDB->beginTransaction() と等価

サービスコンテナが差し替わると、Facadeで使うインスタンスも差し替わります。
けど、このようにすると

DB::enableQueryLog(); // 差し替え前に1回使っている

$defaultDb = $this->app->make('db'); // デフォルトインスタンス
$mockDb = new DBConnectionWithoutTransaction($defaultDb); // 代替インスタンス
$this->app->instance('db', $mockDb); // 差し替える

DB::beginTransaction(); // $defaultDb ->beginTransaction() と等価

差し替わりません。あれ?おかしいな…。

DB::enableQueryLog(); // 差し替え前に1回使っている

$defaultDb = $this->app->make('db'); // デフォルトインスタンス
$mockDb = new DBConnectionWithoutTransaction($defaultDb); // 代替インスタンス
$this->app->instance('db', $mockDb); // 差し替える

DB::beginTransaction(); // $defaultDb ->beginTransaction() と等価

$witch = $this->app->make('db'); // $mockDb が返ってくる。差し替わっている

サービスコンテナの中身はちゃんと差し替わっています。
ポイントは最初の DB::enableQueryLog(); です。
ファサードのソースコードを見ると、

vendor\laravel\framework\src\Illuminate\Support\Facades\Facade.php
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }
        // すでにキャッシュしていればそれを返す
        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }
        // サービスコンテナからインスタンスを取ってきてキャッシュする
        return static::$resolvedInstance[$name] = static::$app[$name];
    }

このように、サービスコンテナから取得したインスタンスを独自にキャッシュしているんですね。
というわけで、サービスコンテナを差し替えたとき、それよりも前に1回でも同じFacadeを使っているなら、それをクリアする必要があります。

DB::enableQueryLog(); // ここで $defaultDb がキャッシュされる

$defaultDb = $this->app->make('db'); // デフォルトインスタンス
$mockDb = new DBConnectionWithoutTransaction($defaultDb); // 代替インスタンス
$this->app->instance('db', $mockDb); // 差し替える
Facade::clearResolvedInstance('db'); // キャッシュをクリアする

DB::beginTransaction(); // $mockDb->beginTransaction() と等価。差し替わっている。

おまけ DBConnectionWithoutTransaction の中身

ばばばーっと作ったものなのであまりお見せするようなものではありませんが、参考までに。
今回のように「とりあえず必要なメソッドだけラフに置き換えたい」という場合は、このように、

  • 元のクラスの interface を引き継ぐ
  • コンストラクタで元のオブジェクトを受け取れるようにして、メンバ変数にストック(ラッパクラスにする)
  • interface にあるメソッドを実装し、中身はすべて、メンバにストックした元のオブジェクトをコールする。
  • それ以外のメソッドも呼ばれるかもしれないので __call を実装して同様にスルーパス。
  • 置き換えが必要なメソッドを書く。

といった感じにすると、手軽にうまく差し替えできるかと思います。

#テストのときにしか使わないので tests に配置しましたが、ご利用の際はご自由に(^^)

tests\DBConnectionWithoutTransaction.php
namespace Tests;

use Closure;
use Illuminate\Database\ConnectionResolverInterface;

/**
 * トランザクションを抑制したデータベースサービス
 */
class DBConnectionWithoutTransaction implements ConnectionResolverInterface
{
    private $base;

    public function __construct(ConnectionResolverInterface $manager)
    {
        $this->base = $manager;
    }

    public function connection($name = null)
    {
        return $this->base->connection($name);
    }

    public function getDefaultConnection()
    {
        return $this->base->getDefaultConnection();
    }

    public function setDefaultConnection($name)
    {
        return $this->base->setDefaultConnection($name);
    }

    public function __call($method, $parameters)
    {
        return $this->base->$method(...$parameters);
    }

    public function transaction(Closure $callback, $attempts = 1)
    {
        return;
    }

    public function beginTransaction()
    {
        return;
    }

    public function commit()
    {
        return;
    }

    public function rollBack()
    {
        return;
    }

    public function transactionLevel()
    {
        return;
    }
}

感想

以前、サービスコンテナの解説記事で「標準機能をそっくり置き換えできる」と書いたことがあって、実際にそれはとてもカンタンで、余裕でFacadeを置き換えしようとしたら ドハマリ したので、勢いのまま筆を執らせていただきました。何か問題に詰まって検索してたどり着くような記事ではないので、読み物としてサラッと流して、頭の片隅に「こんな記事あったなぁ」と覚えておいていただけると、もしものときに役に立つかもしれません……。

いやー、しかしサービスコンテナはそこそこわかっているつもりでしたが、ただの「つもり」でした。なかなか奥が深い。勉強になりました。

しかし最近は Facade なんて使ってんじゃねぇよ!という風潮もあるようで、それはそれでとても確かなのですが、濫用しなければ、そのすっきりとした記法、作るのがカンタン、AppServiceProviderがカオス化しにくい、「自動シングルトン」や「モック化」といった独自の機能などにはそれなりのメリットがあるんじゃないかと思うので、見捨てないで使ってやってほしいなぁと思っています(いちばんのメリットは「記法がスッキリ」だと思いますけどね。僕はわりと好きです)。

コンストラクタインジェクション(テストしやすく長期メンテナンス性が高い)
use App\MyServices\SampleService;

class MyAppUsecase
{
    protected $sampleService;

    public function __construct(SampleService $sampleService)
    {
        $this->sampleService = $sampleService;
    }

    public function execUsecase()
    {
        $this->sampleService->exec();
    }
Facade(スッキリ)
use App\MyFacades\SampleServiceFacade;

class MyAppUsecase
{
    public function execUsecase()
    {
        SampleServiceFacade::exec();
    }

他にもこんな記事を書いています

Laravelのちょっとマニアックな視点から、誰も書かない記事を書いています(笑
合わせてご覧いただけると幸いです(^^)

ファサードを作る

今回と似た内容の「ヘルパ関数編」

サービスコンテナ講座

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む