20200227のPHPに関する記事は18件です。

【PHP】array(連想配列)とobjectの違い

array(連想配列)

$array = [
    "school" => "日本高校",
    "class" => "A",
];

echo $array[“school”];
// 日本高校

オブジェクト

class object{
    public $school = "日本高校";
    public $class = "A";
}
$objectSchool = new object;
echo $objectSchool->school;
// 日本高校

確認方法

var_dumpして先頭にarrayとobjectのどちらが付いているか

注意すること

呼び出し方が違う(上記コード参照)

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

PHPで十進法->二進法に変換する

PHPで十進法->二進法に変換するコードを書いてみました。
decbin()関数があリますが。

binary.php
<?php

  $input = fgets(STDIN);
  $binary = array();

  while(true){
    $remainder = $input % 2;
    $input = floor($input/2);
    $binary[] = $remainder;
    if ($input == 0 || $input == 1){
      $binary[] = $input;
      break;
    }  
  }
  foreach(array_reverse($binary) as $bit){
    echo $bit;
  }
}
?>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文系ガチの未経験だった僕が自力4ヶ月弱で自社開発のweb系会社で新卒内定をもらうために行ったこと

誰に読んで欲しいか

・文系新卒未経験の方
・web系の会社で働きたい方
・フリーランスでアソビタィ!!

偉そうに書きますがご容赦ください><

なぜ書くのか

実際に自分がエンジニアになりたいと思った時に情報が全くなかったから。
何をしなければいけないかが全くわからなかった。

web系で就職した人や未経験理系卒の方の情報は多少あれど文系未経験新卒でweb系エンジニアになった人の情報は全くなかったため。しかし文系卒のエンジニア志望は増えて来ていてニーズはかなりあると考えた。

自己紹介

早稲田大学文系3年生。
長所は考えた上で実際に行動にすぐに移せること。
大学生時代は酒飲み、遊び、そしてオーストラリアとニュージーランドでワーキングホリデーを行い、ガチガチの現地の環境に身を置いて生活をした。
その時に初めてテクノロジーに触れて衝撃を受け、ITに興味を少し持つ。
ものを作ってびっくりさせることが好き。

自分が行ったこと

3年生の夏

コンサル目指してサマーインターンを応募しまくるも落選落選。
→自己分析を重ねてどう考えても仕事よりノマドになって海外で自由に働きたいと強い決意。

10月前半

・週3働いていたマーケのインターンを辞め、飲み会などを減らし、未来への投資として勉強する。
実際にprogateという初心者向けの学習サイトで月額1000円払ってHTML,CSSを2週ほどした。
 →一周2日あれば余裕
 →月額1000円はスクールと比較すればどう考えても格安
・その後実際に自分の作ってみたいサイトを5つほどアウトプットしてみる
・その後同じものでJavaScriptを学び、難しかったので5週ほどした。

10月後半

・インターンを視野にいれて重役のいる大手コンサルでの長期インターンの条件としてあげられた制作物の作成に励む。
 ・課題の内容としてはとあるチャットアプリのAPIを使ったアプリ内メッセージ検索webアプリ
 ・AWSを使ったデプロイまで行うよう指示される
 ・文系なのでエンジニアの友人もいなかったため小学校の時の友人、MatcherというOB訪問アプリで 出会った社会人の方、前職場のエンジニアの方に再コンタクト
 ・正直かなり大変でエラー1つに数日かけることもしばしば
・Vue.jsというjavascript(js)の指定が確かあったため、Udemyというオンライン学習用動画を販売するサイトを利用し、1000円で購入し、課題と同時並行で勉強した。

11月後半

課題完成し、OKが出たが人数の関係で引き伸ばされた。
→しょぼいですがもしお時間あれば見てみてください..まだまだ改良中なのでお手柔らかに..
slackのメッセージ検索アプリ

早く実務経験を積みたかったため、諦めて自分でインターンを探し始める。
この時使ったものはMatcher,キャリアバイト,Infraというサービスたち。
まずエンジニアとしての働き方などベースを知るためにMatcherで20人ほど文系未経験卒のエンジニアの方中心に話を聞いた。

12月

その後早速実務未経験でも働けそうなインターンに応募。面接させてくれた企業は3つ。
1つ目の企業には面接中に断られる。

ここでフィードバックをお願いして教えてもらったこと
・普通に企業の目線で考えるとエンジニアを雇う意味は2つ。
 ❶即戦力で企業に利益をもたらすため
 ❷就職するかもしれないため将来を見越した研修込みの投資として

今このページを読んでいる人はおそらく❷を狙うしかない。
だから自分はそれ以降就職してもいい会社しか応募せず、面接も本気で就活のレベルで受けた。

12月後半

ゲームメディアを運営する企業で採用された。フロントでの努力を評価されバックエンドのPHPを使ったエンジニアとして研修込みでの採用。
同時にレバテックという人材サービスを利用し就活を視野に入れて自己分析をもっと極める。

1月

長期インターン開始。
後半からは面接も平日夜に毎日受ける。
→フィードバックを最大限に生かしてPDCA回して面接の受けが良くなっていく。

2月

ついに複数の企業から内定をいただく。
現在まだ一応他の企業も見ている。

すいません。日記のような形になりました。

まとめ

就職までの時間を考えて何をすべきか逆算していくことが大事。
正直自分は実務経験ないと就職できないと思ってインターンを必死に探したが、しなくてももしかしたらよかったかもしれない。
しかしインターンをして就活しながらも現在進行形で技術に触れていることをアピールできたのは大きかった所感。

インターンを受けるにあたって大事なことは
ちゃんと就職してもいい企業で受けると互いに良くて面接ももちろん通る。

就活では
・しっかりエンジニアになりたい理由を固めることは大前提必要。

・その上でそのために実際にこんなもの作って、インターンもやっている(何かしら活動している)から私はこれからももちろんエンジニアでゴリゴリ開発していきますよ?だって書くの好きだもん!ということを伝えることが絶対に必要。

おそらく面接官が心配していることは、
❶エンジニアなりたいのはわかったけど続けれるのか。文系やめる人多いけど。
❷成長は早くて遅い経験有りのエンジニアを雇うよりも将来的に利益になるのか。

ということ。
あとは文系ならでは(?)のコミュニケーション能力を面接で抜群に発揮して内定をもぎ取りましょう!!

感想

初めてのqiita投稿でした。
ずっと前から自分が情報なくて困っていたのでこういったことを内定もらったらすぐにしたいと考えていて、それが実行できたので嬉しいです。

次回は初心者から見た簡単なIT業界やエンジニアの中の詳しい職種の特徴を発信したいと思います。
もちろんコーディングの知識もアウトプットしたいと考えてます。

お手柔らかに、意見やコメントもらえると嬉しいです!
ありがとうございました。

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

文系ガチの未経験エンジニアだった僕が自力4ヶ月弱で自社開発のweb系会社で新卒内定をもらうために行ったこと

誰に読んで欲しいか

・文系新卒未経験の方
・web系の会社で働きたい方
・フリーランスでアソビタィ!!

偉そうに書きますがご容赦ください><
右に目次があるので興味あるとこのみピックアップできます!!

なぜ書くのか

実際に自分がエンジニアになりたいと思った時に情報が全くなかったから。
何をしなければいけないかが全くわからなかった。

web系で就職した人や未経験理系卒の方の情報は多少あれど文系未経験新卒でweb系エンジニアになった人の情報は全くなかったため。しかし文系卒のエンジニア志望は増えて来ていてニーズはかなりあると考えた。

自己紹介

早稲田大学文系3年生。
長所は考えた上で実際に行動にすぐに移せること。
大学生時代は酒飲み、遊び、そしてオーストラリアとニュージーランドでワーキングホリデーを行い、ガチガチの現地の環境に身を置いて生活をした。

なぜエンジニアになりたいか

2年生までは「MacBook=レポートを作成できる優れモノ」だったが、
ワーホリで初めてキャッシュレスというテクノロジーに触れて衝撃を受け、生活の質を劇的に変えることのできるITに興味を少し持つ。
誰かの役に立つものを作ること。喜んでもらうこと。びっくりさせることが好きだから。
あとは成長が積み上げ式でわかる。営業を2年間やっていたが状況によって結果が大きく異なる。エンジニアのスキルは昔のコード見ると明らかに質が低いのがわかる。(四ヶ月目までの今の時期だけかもしれないが)

自分が行ったこと(具体的に)

3年生の夏

コンサル目指してサマーインターンを応募しまくるも落選落選。
→自己分析を重ねてどう考えても仕事よりノマドになって海外で自由に働きたいと強い決意。

10月前半

・週3働いていたマーケのインターンを辞め、飲み会などを減らし、未来への投資として勉強する。
実際にprogateという初心者向けの学習サイトで月額1000円払ってHTML,CSSを2週ほどした。
 →一周2日あれば余裕
 →月額1000円はスクールと比較すればどう考えても格安
・その後実際に自分の作ってみたいサイトを5つほどアウトプットしてみる
・その後同じものでJavaScriptを学び、難しかったので5週ほどした。

10月後半

・インターンを視野にいれて重役のいる大手コンサルでの長期インターンの条件としてあげられた制作物の作成に励む。
 ・課題の内容としてはとあるチャットアプリのAPIを使ったアプリ内メッセージ検索webアプリ
 ・AWSを使ったデプロイまで行うよう指示される
 ・文系なのでエンジニアの友人もいなかったため小学校の時の友人、MatcherというOB訪問アプリで 出会った社会人の方、前職場のエンジニアの方に再コンタクト
 ・正直かなり大変でエラー1つに数日かけることもしばしば
・Vue.jsというjavascript(js)の指定が確かあったため、Udemyというオンライン学習用動画を販売するサイトを利用し、1000円で購入し、課題と同時並行で勉強した。

11月後半

課題完成し、OKが出たが人数の関係で引き伸ばされた。
→しょぼいですがもしお時間あれば見てみてください..まだまだ改良中なのでお手柔らかに..
slackのメッセージ検索アプリ

早く実務経験を積みたかったため、諦めて自分でインターンを探し始める。
この時使ったものはMatcher,キャリアバイト,Infraというサービスたち。
まずエンジニアとしての働き方などベースを知るためにMatcherで20人ほど文系未経験卒のエンジニアの方中心に話を聞いた。

12月

その後早速実務未経験でも働けそうなインターンに応募。面接させてくれた企業は3つ。
1つ目の企業には面接中に断られる。

ここでフィードバックをお願いして教えてもらったこと
・普通に企業の目線で考えるとエンジニアを雇う意味は2つ。
 ❶即戦力で企業に利益をもたらすため
 ❷就職するかもしれないため将来を見越した研修込みの投資として

今このページを読んでいる人はおそらく❷を狙うしかない。
だから自分はそれ以降就職してもいい会社しか応募せず、面接も本気で就活のレベルで受けた。

12月後半

ゲームメディアを運営する企業で採用された。フロントでの努力を評価されバックエンドのPHPを使ったエンジニアとして研修込みでの採用。
同時にレバテックという人材サービスを利用し就活を視野に入れて自己分析をもっと極める。

1月

長期インターン開始。
後半からは面接も平日夜に毎日受ける。
→フィードバックを最大限に生かしてPDCA回して面接の受けが良くなっていく。

2月

ついに複数の企業から内定をいただく。
現在まだ一応他の企業も見ている。

すいません。日記のような形になりました。

まとめ

就職までの時間を考えて何をすべきか逆算していくことが大事。
正直自分は実務経験ないと就職できないと思ってインターンを必死に探したが、しなくてももしかしたらよかったかもしれない。
しかしインターンをして就活しながらも現在進行形で技術に触れていることをアピールできたのは大きかった所感。

インターンを受けるにあたって大事なことは
ちゃんと就職してもいい企業で受けると互いに良くて面接ももちろん通る。

就活では
・しっかりエンジニアになりたい理由を固めることは大前提必要。

・その上でそのために実際にこんなもの作って、インターンもやっている(何かしら活動している)から私はこれからももちろんエンジニアでゴリゴリ開発していきますよ?だって書くの好きだもん!ということを伝えることが絶対に必要。

おそらく面接官が心配していることは、
❶エンジニアなりたいのはわかったけど続けれるのか。文系やめる人多いけど。
❷成長は早くて遅い経験有りのエンジニアを雇うよりも将来的に利益になるのか。

ということ。
あとは文系ならでは(?)のコミュニケーション能力を面接で抜群に発揮して内定をもぎ取りましょう!!

感想

初めてのqiita投稿でした。
ずっと前から自分が情報なくて困っていたのでこういったことを内定もらったらすぐにしたいと考えていて、それが実行できたので嬉しいです。

次回は初心者から見た簡単なIT業界やエンジニアの中の詳しい職種の特徴を発信したいと思います。
もちろんコーディングの知識もアウトプットしたいと考えてます。

お手柔らかに、意見やコメントもらえると嬉しいです!
ありがとうございました。

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

【PHP】CSVファイルを読み込む方法

はじめに

アプリケーションを作成しているとcsvファイルを読み込み、書き込みする場面が多くあると思います。
今回はcsvファイルを読み込み、書き込みする方法を解説します。

今回使用するcsvファイル 祝日のデータ

内閣府が出している祝日のファイルを使って解説します。

中身は下記のような形式です。

syukujitu.csv
1955/1/1,元日
1955/1/15,成人の日
1955/3/21,春分の日
  // :
  // :
2021/10/11,スポーツの日
2021/11/3,文化の日
2021/11/23,勤労感謝の日

csvファイルの読み込み

csvファイルを読み込む方法の大まかな流れは下記の通りです。

  1. csvファイルを開く
  2. csvファイルを読み込む
  3. csvファイルを閉じる

1. csvファイルを開く

まず、csvファイルを読み込むためにファイルを開きます。
使用する関数はfopenです。
この関数の処理に成功するとファイルポインタリソースが返ってきます。
ファイルポインタリソースとはファイルの読み書きに必要な情報(ファイル名、ファイル操作権限、csvファイルの何行目から読むのかなど)です

sample.php
<?php
$filePath = "sample.csv";
$handle = fopen($filePath, "r");

$handleの意味は$filePathのファイルをrモードで開くということです

モードについて

ファイルを開くにも色々な種類があります。
書き込み専用で開く、読み込み専用で開く、書き込みと読み込みできるように開くなどです
4種類について説明します。これだけで基本的な操作はできます

r:読み込みのみで開きます。処理の開始場所はファイルの先頭です。
r+:読み込み/書き出し用に開きます。 処理の開始場所はファイルの先頭です。
a:書き出し用のみで開きます。処理の開始場所はファイルの終端です。 ファイルが存在しない場合には、作成を試みます。
a+:読み込み/書き出し用で開きます。 処理の開始場所はファイルの終端です。 ファイルが存在しない場合には、作成を試みます。

2. csvファイルを読み込む

1で開いた結果を使ってcsvファイルの読み込みを行います
fgetcsv関数を使用します。
下記のコードでcsvファイルを読み込みます。
$handleの情報を元に,区切りのcsvファイルを1000項目まで読み込みます
1000は0にすると無制限に読み込んでくれます。しかし、処理が遅くなるようなので、設定しているのが無難です

sample.php
<?php
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
    echo $data[0];
    echo $data[1] . "<br />\n";
}

3. csvファイルを閉じる

読み込みが終わったら、ファイルを閉じます。
使用する関数はfcloseです

sample.php
<?php
fclose($handle);

文字コード

csvファイルはShift_JISで書かれていることが多いので、プログラムで扱うためには文字コードをUTF-8に変換する必要があることが多いです。
文字コードの変換方法は下記の通りです。

sample.php
<?php
//Shift_JISからutf-8に変換
$data[1] = mb_convert_encoding($data[1],"utf-8","sjis");

以上です!!!
ここまで読んでいただきありがとうございました!!
疑問、気になるところがございましたら、質問、コメントよろしくお願いします!!!

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

RHEL7でyum update 時にPHP5.4がアップデート出来ない場合の対応

RedHat Enterprise Linux 7(RHEL7)で、yum updateしようとした際に次のようなメッセージが表示され、php5.4のアップデートに失敗する場合があります。

# yum update
読み込んだプラグイン:langpacks, product-id, search-disabled-repos, subscription-manager
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ php.x86_64 0:5.4.16-46.el7 を 更新
---> パッケージ php.x86_64 0:5.4.16-46.1.el7_7 を アップデート
---> パッケージ php-cli.x86_64 0:5.4.16-46.el7 を 更新
--> 依存性の処理をしています: php-cli(x86-64) = 5.4.16-46.el7 のパッケージ: php-devel-5.4.16-46.el7.x86_64
---> パッケージ php-cli.x86_64 0:5.4.16-46.1.el7_7 を アップデート
---> パッケージ php-common.x86_64 0:5.4.16-46.el7 を 更新
--> 依存性の処理をしています: php-common(x86-64) = 5.4.16-46.el7 のパッケージ: php-mbstring-5.4.16-46.el7.x86_64
---> パッケージ php-common.x86_64 0:5.4.16-46.1.el7_7 を アップデート
---> パッケージ php-gd.x86_64 0:5.4.16-46.el7 を 更新
---> パッケージ php-gd.x86_64 0:5.4.16-46.1.el7_7 を アップデート
---> パッケージ php-mysql.x86_64 0:5.4.16-46.el7 を 更新
---> パッケージ php-mysql.x86_64 0:5.4.16-46.1.el7_7 を アップデート
---> パッケージ php-pdo.x86_64 0:5.4.16-46.el7 を 更新
---> パッケージ php-pdo.x86_64 0:5.4.16-46.1.el7_7 を アップデート
---> パッケージ php-process.x86_64 0:5.4.16-46.el7 を 更新
---> パッケージ php-process.x86_64 0:5.4.16-46.1.el7_7 を アップデート
---> パッケージ php-xml.x86_64 0:5.4.16-46.el7 を 更新
---> パッケージ php-xml.x86_64 0:5.4.16-46.1.el7_7 を アップデート
---> パッケージ php-xmlrpc.x86_64 0:5.4.16-46.el7 を 更新
---> パッケージ php-xmlrpc.x86_64 0:5.4.16-46.1.el7_7 を アップデート
--> 依存性解決を終了しました。
エラー: パッケージ: php-devel-5.4.16-46.el7.x86_64 (@rhel-7-server-optional-rpms)
             要求: php-cli(x86-64) = 5.4.16-46.el7
            削除中: php-cli-5.4.16-46.el7.x86_64 (@rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-46.el7
            次のものにより更新された: : php-cli-5.4.16-46.1.el7_7.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-46.1.el7_7
            利用可能: php-cli-5.4.16-21.el7.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-21.el7
            利用可能: php-cli-5.4.16-23.el7_0.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-23.el7_0
            利用可能: php-cli-5.4.16-23.el7_0.1.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-23.el7_0.1
            利用可能: php-cli-5.4.16-23.el7_0.3.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-23.el7_0.3
            利用可能: php-cli-5.4.16-36.el7_1.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-36.el7_1
            利用可能: php-cli-5.4.16-36.1.el7_2.1.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-36.1.el7_2.1
            利用可能: php-cli-5.4.16-36.3.el7_2.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-36.3.el7_2
            利用可能: php-cli-5.4.16-42.el7.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-42.el7
            利用可能: php-cli-5.4.16-43.el7_4.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-43.el7_4
            利用可能: php-cli-5.4.16-43.el7_4.1.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-43.el7_4.1
            利用可能: php-cli-5.4.16-45.el7.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-45.el7
エラー: パッケージ: php-mbstring-5.4.16-46.el7.x86_64 (@rhel-7-server-optional-rpms)
             要求: php-common(x86-64) = 5.4.16-46.el7
            削除中: php-common-5.4.16-46.el7.x86_64 (@rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-46.el7
            次のものにより更新された: : php-common-5.4.16-46.1.el7_7.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-46.1.el7_7
            利用可能: php-common-5.4.16-21.el7.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-21.el7
            利用可能: php-common-5.4.16-23.el7_0.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-23.el7_0
            利用可能: php-common-5.4.16-23.el7_0.1.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-23.el7_0.1
            利用可能: php-common-5.4.16-23.el7_0.3.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-23.el7_0.3
            利用可能: php-common-5.4.16-36.el7_1.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-36.el7_1
            利用可能: php-common-5.4.16-36.1.el7_2.1.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-36.1.el7_2.1
            利用可能: php-common-5.4.16-36.3.el7_2.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-36.3.el7_2
            利用可能: php-common-5.4.16-42.el7.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-42.el7
            利用可能: php-common-5.4.16-43.el7_4.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-43.el7_4
            利用可能: php-common-5.4.16-43.el7_4.1.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-43.el7_4.1
            利用可能: php-common-5.4.16-45.el7.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-45.el7
**********************************************************************
yum can be configured to try to resolve such errors by temporarily enabling
disabled repos and searching for missing dependencies.
To enable this functionality please set 'notify_only=0' in /etc/yum/pluginconf.d/search-disabled-repos.conf
**********************************************************************

--> トランザクションの確認を実行しています。
---> パッケージ kernel.x86_64 0:3.10.0-693.5.2.el7 を 削除
---> パッケージ kernel-devel.x86_64 0:3.10.0-693.5.2.el7 を 削除
---> パッケージ php-cli.x86_64 0:5.4.16-46.el7 を 更新
--> 依存性の処理をしています: php-cli(x86-64) = 5.4.16-46.el7 のパッケージ: php-devel-5.4.16-46.el7.x86_64
---> パッケージ php-common.x86_64 0:5.4.16-46.el7 を 更新
--> 依存性の処理をしています: php-common(x86-64) = 5.4.16-46.el7 のパッケージ: php-mbstring-5.4.16-46.el7.x86_64
--> 依存性解決を終了しました。
エラー: パッケージ: php-devel-5.4.16-46.el7.x86_64 (@rhel-7-server-optional-rpms)
             要求: php-cli(x86-64) = 5.4.16-46.el7
            削除中: php-cli-5.4.16-46.el7.x86_64 (@rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-46.el7
            次のものにより更新された: : php-cli-5.4.16-46.1.el7_7.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-46.1.el7_7
            利用可能: php-cli-5.4.16-21.el7.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-21.el7
            利用可能: php-cli-5.4.16-23.el7_0.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-23.el7_0
            利用可能: php-cli-5.4.16-23.el7_0.1.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-23.el7_0.1
            利用可能: php-cli-5.4.16-23.el7_0.3.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-23.el7_0.3
            利用可能: php-cli-5.4.16-36.el7_1.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-36.el7_1
            利用可能: php-cli-5.4.16-36.1.el7_2.1.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-36.1.el7_2.1
            利用可能: php-cli-5.4.16-36.3.el7_2.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-36.3.el7_2
            利用可能: php-cli-5.4.16-42.el7.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-42.el7
            利用可能: php-cli-5.4.16-43.el7_4.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-43.el7_4
            利用可能: php-cli-5.4.16-43.el7_4.1.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-43.el7_4.1
            利用可能: php-cli-5.4.16-45.el7.x86_64 (rhel-7-server-rpms)
                php-cli(x86-64) = 5.4.16-45.el7
エラー: パッケージ: php-mbstring-5.4.16-46.el7.x86_64 (@rhel-7-server-optional-rpms)
             要求: php-common(x86-64) = 5.4.16-46.el7
            削除中: php-common-5.4.16-46.el7.x86_64 (@rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-46.el7
            次のものにより更新された: : php-common-5.4.16-46.1.el7_7.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-46.1.el7_7
            利用可能: php-common-5.4.16-21.el7.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-21.el7
            利用可能: php-common-5.4.16-23.el7_0.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-23.el7_0
            利用可能: php-common-5.4.16-23.el7_0.1.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-23.el7_0.1
            利用可能: php-common-5.4.16-23.el7_0.3.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-23.el7_0.3
            利用可能: php-common-5.4.16-36.el7_1.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-36.el7_1
            利用可能: php-common-5.4.16-36.1.el7_2.1.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-36.1.el7_2.1
            利用可能: php-common-5.4.16-36.3.el7_2.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-36.3.el7_2
            利用可能: php-common-5.4.16-42.el7.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-42.el7
            利用可能: php-common-5.4.16-43.el7_4.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-43.el7_4
            利用可能: php-common-5.4.16-43.el7_4.1.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-43.el7_4.1
            利用可能: php-common-5.4.16-45.el7.x86_64 (rhel-7-server-rpms)
                php-common(x86-64) = 5.4.16-45.el7
 問題を回避するために --skip-broken を用いることができます。
 これらを試行できます: rpm -Va --nofiles --nodigest

原因

rhel-7-server-optional-rpms リポジトリ(チャンネル)がenableでないと、当該事象が発生します。
上記リポジトリがenableであるかどうかは、yum repolistコマンドで確認することができます。

以下のような状態である(rhel-7-server-optional-rpmsがenableである)場合は、当該事象は発生しません。
rhel-7-server-optional-rpmsが結果に表示されない場合は、当該事象が発生する可能性があります。

# yum repolist
読み込んだプラグイン:langpacks, product-id, search-disabled-repos, subscription-manager
リポジトリー ID                                                   リポジトリー名                                                              状態
rhel-7-server-optional-rpms/7Server/x86_64                        Red Hat Enterprise Linux 7 Server - Optional (RPMs)                         19,656
rhel-7-server-rpms/7Server/x86_64                                 Red Hat Enterprise Linux 7 Server (RPMs)                                    27,011

対応

以下のコマンドを実行し、rhel-7-server-optional-rpmsリポジトリを追加します。

subscription-manager repos --enable rhel-7-server-optional-rpms

注意点

optionalリポジトリは、RHELのサポート対象ではない(RedHatのグローバルサポートサービスを受けられない)ため、導入に際しては注意が必要です。
https://access.redhat.com/ja/solutions/4387631

Red Hat では、Red Hat/Red Hat JBoss 製品の機能に加えて、お客様の利便性を向上させるソフトウェアパッケージ (RPM) を追加で提供しています。これらのソフトウェアパッケージには、オープンソースライセンスのソフトウェア (「Optional」Red Hat Network チャンネル) および商用ライセンスのソフトウェア (「Supplementary」Red Hat Network チャンネル) があります。Optional および Supplementary チャンネルのソフトウェアパッケージはサポート対象ではなく、ABI の保証も適用されません。

RHELでoptionalリポジトリに属するPHPパッケージは次のとおりです。

  • php-bcmath
  • php-devel
  • php-embedded
  • php-enchant
  • php-dba
  • php-fpm
  • php-intl
  • php-mbstring
  • php-mysqlnd
  • php-pspell
  • php-snmp

参考

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

Laravel 暗号化 初期化ベクトルが格納される場所

Laravelにはencryptdecryptという暗号化、復号のヘルパーメソッドが用意されています。

暗号化 6.x Laravel
https://readouble.com/laravel/6.x/ja/encryption.html

暗号化の結果を見ると、同じ文言でも全く違う文字列になっています。ということは、Laravelの暗号化ではランダム生成された初期化ベクトルを使っているということになりますが、データベースには初期化ベクトルのカラムはありません。初期化ベクトルはどこに保存されているのでしょうか。

前提知識

LaravelはAES-256-CBCという方式で暗号化をしています。この記事を理解するためには、AES、CBC、初期化ベクトルについてある程度の知識が必要です。

プログラマの暗号化入門
https://qiita.com/asksaito/items/1793b8d8b3069b0b8d68

Advanced Encryption Standard
https://ja.wikipedia.org/wiki/Advanced_Encryption_Standard

暗号利用モード
https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7%E5%88%A9%E7%94%A8%E3%83%A2%E3%83%BC%E3%83%89

初期化ベクトル
https://ja.wikipedia.org/wiki/%E5%88%9D%E6%9C%9F%E5%8C%96%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB

初期化ベクトル保存場所

encrypt で暗号化したデータに、初期化ベクトルが含まれています。ただ、base64エンコードされているので、そのままでは読めません。encrypt で暗号化したデータをbase64デコードすると以下JSONが得られますが、「iv」に初期化ベクトルがセットされています。

暗号化のbase64デコード結果
{
  "iv":"初期化ベクトル",
  "value":"暗号化された文言",
  "mac":"メッセージ認証符号"
}

動くコードで確認する

説明だけだとしっくりこないと思うので、実際に動くコードで確認しましょう。以下URLに今回使うコード一式をアップロードしました。

https://github.com/kaidouji85/laravel-encrypt-iv

コードの動かし方は、リンク先を参照してください。コードの本体はLaravelカスタムコマンドで、ファイルパスはapp/Console/Commands/EncryptIV.phpです。ボイラープレートのせいで行数が無駄に多いですが、handleメソッドがサンプルコードのメインです。プログラムを実行すると、前セクションで紹介した「暗号化のbase64デコード結果」が出力されます。

app/Console/Commands/EncryptIV.php
public function handle()
    {
        // 勉強用に暗号化前の情報をコンソール出力している
        // ただ、セキュリティ上の問題があるので、本番環境では絶対にこのようなコードを書いてはいけない

        $origin = "オリジナルメッセージ";
        $this->info("origin: {$origin}");

        $encrpted = encrypt($origin);
        $this->info("encrpted: {$encrpted}");

        $base64Decoded = base64_decode($encrpted);
        $this->info("base64Decoded: {$base64Decoded}");
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; WP_MatchesMapRegex has a deprecated constructor in /

⚠️階層ファイルがめちゃくちゃなのは、スルーしてくださいw

WordPressの自作テーマの作り方の講座を見ていて急に出て来たエラー

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; WP_MatchesMapRegex has a deprecated constructor in /Users/Sites/bootstrap_wp_udemy/B2W Final Course Files/B - Download WordPress/b2w/wp-includes/class-wp.php on line 633
Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; Translation_Entry has a deprecated constructor in /Users/Sites/bootstrap_wp_udemy/B2W Final Course Files/B - Download WordPress/b2w/wp-includes/pomo/entry.php on line 14
Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; POMO_Reader has a deprecated constructor in /Users/Sites/bootstrap_wp_udemy/B2W Final Course Files/B - Download WordPress/b2w/wp-includes/pomo/streams.php on line 12

私が使っていたバージョン

PHP 7.3.1
WP version 4.0.1

WPのバージョンが低いとこのようなことが起きるよう。

参照URL
https://www.php.net/manual/ja/migration70.deprecated.php

解決方法

  1. バックアップ(直そうとしているファイルのコピー)をとる。理由はわからなくなって元に戻れないと怖いから。
  2. wp-config.phpとwp-contentフォルダを削除
  3. 最新のWPのバージョンをとる。私が新しくWPのバージョンをとって来たのは、5.3.2。
  4. 新しくとってきたwp-config.phpと新しいwp-contentフォルダを差し替える

参照URL
https://kotori-blog.com/wordpress/upgrade/

これでうまく行きました。
差し替えは怖かったですが、バックアップをとっておいたので安心して実行できました。
めでたし、めでたし。

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

【Laravel】環境構築からSNS連携まで

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

Laravelプロジェクト作成から初めに行うべき設定まとめ

はじめに

Laravelプロジェクトを作成する方法と、初めに行うべき設定(DB設定や日本語化など)をまとめました。

環境

  • macOS Catalina 10.15.3
  • PHP 7.3.11
  • Composer 1.9.3
  • Laravel 6.17.1

プロジェクト作成

ターミナルで以下のコマンドを実行します。

$ composer create-project laravel/laravel --prefer-dist sample
Installing laravel/laravel (v6.12.0)
  - Installing laravel/laravel (v6.12.0): Loading from cache

...

Application key set successfully.

--prefer-distについて

--prefer-distは、Laravelを圧縮(zip形式)してダウンロードするためのオプションになります。
--prefer-distを指定しないと、Laravelを圧縮せずにダウンロードするためダウンロード時間が長くなります。
なお、--prefer-sourceというオプションもあって、これはオプションを指定しない場合、つまりLaravelを圧縮せずにダウンロードするオプションです。

  • --prefer-source:Laravelを圧縮せずにダウンロード(ダウンロード時間が長い)
  • --prefer-dist:Lavavelを圧縮してダウンロード(ダウンロード時間が短い)

Command-line interface / Commands - Composer

DB設定

今回は最も手軽なSQLiteを使用します。

設定ファイル変更

.envを以下のように設定します。

# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
DB_CONNECTION=sqlite

DBファイル作成

ターミナルで以下のコマンドを実行します。

$ sqlite3 database/database.sqlite
sqlite> .table
sqlite> .q

解説

config/database.phpの38〜44行目あたりにSQLiteに関する設定があります。

        'sqlite' => [
            'driver' => 'sqlite',
            'url' => env('DATABASE_URL'),
            'database' => env('DB_DATABASE', database_path('database.sqlite')),
            'prefix' => '',
            'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
        ],

DBの設定ファイルは以下の行で設定しています。

'database' => env('DB_DATABASE', database_path('database.sqlite')),

envメソッドは.envファイルから第1引数で指定した設定を取得します。
第1引数で指定した設定が見つからなかった場合は、第2引数で設定したデフォルト値が適用されます。
今回は.envDB_DATABASEは設定していないので、第2引数で指定したデフォルト値が適用されています。

日本語化

Laravelの日本語化を行います。

プロジェクト全体

config/app.phpを以下のように設定します。

'locale' => 'ja',
'fallback_locale' => 'en',

fallback_localeは、localeの設定値がない場合に適用される設定です。

タイムゾーン

config/app.phpを以下のように設定します。

'timezone' => 'Asia/Tokyo',

メッセージファイル

resources/lang/jaディレクトリを新たに作成します。

$ mkdir resources/lang/ja

下記のページからダウンロードしてきたファイル群を作成したディレクトリにコピーする。

minoryorg/laravel-resources-lang-ja: Laravel 5.5 日本語メッセージファイル一式です。バリデーションやページネーションを日本語化しています。

バージョン管理

GitとGitHubを使用します。

リポジトリ作成

下記のページを参考にしてリポジトリを作成します。

リポジトリを作成する - GitHub ヘルプ

初期設定

ターミナルで以下のコマンドを実行します。

$ git init
$ git remote add origin https://github.com/xxxxx/yyyyyyy.git

コミット

ローカルで変更が発生したときは、ターミナルで以下のコマンドを実行します。

$ git add .
$ git commit -m "Commit message"
$ git push origin master

.gitignore

.gitignoreは初めから用意されています。
初期設定は以下の通りです。

/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log

まとめ

最低限、これだけは行っておいたほうがいい設定をまとめてみました。
間違いなどありましたらご指摘ください?

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

php + php-fpm + nginx のDatadog APM設定でハマった話

前提

DatadogのAPMでphpのアプリケーションログを取得したい。
環境はちとレガシー。

環境

項目 詳細
環境 VMWare
OS CentOS 6.8
言語 PHP 5.6.40
ミドルウェア PHP-FPM 5.6.40 , Nginx/1.11.1
APM Datadog 7.17.1

手順

基本的には 本家のPHP APMのページを の通りに実施。

事象

DatadogのAPMに全くログが表示されない。。。

/var/log/datadog/trace-agent.logを見るにたまに中身が入っていないログが出てましたが、
これはdd-doctor.phpを実行した際に出力されるもののようでした。

原因

dd-trace-phpからdatadogのtrace-agentへの通信に異様に時間がかかり、タイムアウトしてしまう。
ちなみにCentOS7以降であれば大丈夫そうでした。

おま環はありそう。

動作や設定確認

環境構築が終わった後の動作確認。
とりあえず一通り問題なし。

phpのmodule

$ php -m | grep ddtrace
ddtrace
$ php --ri=ddtrace

ddtrace

Datadog PHP tracer extension
For help, check out the documentation at https://docs.datadoghq.com/tracing/languages/php/
(c) Datadog 2019

Datadog tracing support => enabled
Version => 0.40.0

Directive => Local Value => Master Value
ddtrace.disable => Off => Off
ddtrace.internal_blacklisted_modules_list => ionCube Loader,newrelic, => ionCube Loader,newrelic,
ddtrace.request_init_hook => /opt/datadog-php/dd-trace-sources/bridge/dd_wrap_autoloader.php => /opt/datadog-php/dd-trace-sources/bridge/dd_wrap_autoloader.php
ddtrace.strict_mode => Off => Off

Datadog trace-agentが動いているか

$ netstat -anp | grep 8126
tcp 0 0 127.0.0.1:8126 0.0.0.0:* LISTEN 30926/trace-agent

$ lsof -i:8126
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
trace-age 30926 dd-agent 6u IPv4 131873793 0t0 TCP vm-cent6:8126 (LISTEN)

$ curl localhost:8126
404 page not found

dd-doctor.phpの結果もオールグリーン

DataDog trace extension verification

- PHP version and SAPI                               [5.6.25 - fpm-fcgi]
- ddtrace extension installed                        [OK]
- ddtrace version (installed)                        [0.40.0]
- ddtrace version (const)                            [0.40.0]
- ddtrace version (userland)                         [0.40.0]
- ddtrace versions in sync                           [OK]
- dd_trace() function available                      [OK]
- dd_trace_env_config() function available           [OK]
- ddtrace.request_init_hook set                      [OK]
- ddtrace.request_init_hook reachable                [OK]
- ddtrace.request_init_hook has run                  [OK]
- 'open_basedir' allows access to '/proc/self/'      [OK]
- IntegrationsLoader exists                          [OK]
- Integrations not loaded yet                        [OK]
- Registering an autoloader...
- Integrations loaded                                [OK]
- DDTrace\Tracer class exists                        [OK]
- Background sender is enabled?                      [YES]
- Configured Agent host                              [localhost]
- Configured Agent port                              [8126]
- Agent can receive traces                           [OK]

trace-agent.logを見るとたまに下記のようなアクセス来てるっぽいログが残っているけどアプリケーション情報的なものが入って無さそう。

$ less /var/log/datadog/trace-agent.log
| TRACE | INFO | (pkg/trace/info/stats.go:101 in LogStats) | No data received
| TRACE | INFO | (pkg/trace/info/stats.go:108 in LogStats) | [] -> traces received: 0, traces filtered: 0, traces amount: 4 bytes, events extracted: 0, events sampled: 0

tcpdumpを見てもtrace-agentに通信が来ている様子はなし。

$ tcpdump -i lo -nn port 8126
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes


下記のissueを参考にcurlでtrace-agentに擬似的なデータを流してみます。

https://github.com/DataDog/dd-trace-php/issues/582

$ curl localhost:8126/v0.4/traces \
    -X POST \
    -H 'Content-Type: application/json' \
    -d '[[{"span_id":7888852067085145860,"trace_id":8410276558263903151,"parent_id":0,"name":"symfony.request","service":"some_service","type":"web","resource":"/","start":1568281716000000000,"duration":265471}]]'

これを実施した感じだとtrace-agentに通信がいっているようでちょっと待ったらDatadogのWebコンソールにもアプリケーションログが出てきました。
trace-agent側は問題無さそうで通信がいけば飛びそうだなー

デバッグログの確認

とりあえずデバッグログを出すようにして確認。
Nginxにfastcgi_paramsで設定追加。

Tracing PHP Applications - Configuration - NGINX

fastcgi_param  DD_TRACE_ENABLED true;

その後、Nginxとphp-fpmを再起動。
デバッグログはNginxのerror.logの設定に従って出力されています。

下記の通りのエラーが出力されている。

PHP message: [2020-03-02T10:58:25+09:00] [ddtrace] [error] - Reporting of spans failed: 28 / Timeout was reached (TIMEOUT_MS=1000, CONNECTTIMEOUT_MS=500)
PHP message: [2020-03-02T10:58:25+09:00] [ddtrace] [error] - * Closing connection #0
* Timeout was reached" while reading upstream, client: xxx.xxx.xxx.xxx, server: dd-test.com, request: "GET /info.php HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "dd-test.com"

タイムアウトになっているっぽい。。。
(ちなみにTIMEOUT_MSはデフォルト100(ms)だったのを1000にしていましたがタイムアウトしてました)

タイムアウト値を延ばしてみる

fastcgi_param DD_TRACE_AGENT_CONNECT_TIMEOUT 10000;
fastcgi_param DD_TRACE_AGENT_TIMEOUT 10000;
| TRACE | INFO | (pkg/trace/info/stats.go:108 in LogStats) | [lang:php lang_version:5.6.25 interpreter:fpm-fcgi tracer_version:0.40.0] -> traces received: 2, traces filtered: 0, traces amount: 359761 bytes, events extracted: 2, events sampled: 2

あー、なんかそれっぽいの飛んだ。
tcpdumpにも反応が見えました!!
DatadogのWebコンソールからもアプリケーションログが確認できましたー

という事でタイムアウト値を延ばす事で対応できました:relaxed:
タイムアウト値自体は適切な値を検証する必要がありそうですが。

わかって見ればなんて事のない事象でしたが初めてのAPM導入という事もあり、けっこうハマっちゃいました:mask:

ちなみに

つい先日リリースされたdd-trace-phpの0.41.0には DD_TRACE_BETA_SEND_TRACES_VIA_THREAD というオプションが追加されてました。

これを有効にすると trace-agentへの通信をバックグラウンドで実行してくれて、タイムアウト値を調整せずとも今回のような事象を解消してくれるようです。

DataDog/dd-trace-php

なんかめちゃくちゃタイムリーというか、issueには見当たらなかったけどけっこう要望があったのだろうか。

タイムアウト値を不要に延ばすよりはこっちのオプションで対応がスマートなのかなー
オプション名にめちゃめちゃBETAって付いているのが気になりますが。

と思っていたら このプルリク を見る限りはGAになった時に DD_TRACE_BGS_ENABLED に置き換えられるのかな。

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

XLSXWriterの導入

参考

環境

  • PHP 5.6
  • Laravel 5.4
  • LaravelExcel 2.1
  • XLSXWriter

LaravelExcelを利用し、DBから取得したデータをExcelファイルに書き込んでExportする処理を作っていたが、データ件数が大量になると

Allowed memory size of ○○○○○ bytes exhausted

というメモリオーバーのエラーが出た。

PHPでExcelファイルを作成する際にはしばしばぶち当たるこの問題。
今回は顧客の要望をまったく満たせないパフォーマンスだったので、ライブラリをXLSXWriterに変更してみた。

パッケージのインストール

composer require mk-j/PHP_XLSXWriter

簡単な使い方

use XLSXWriter;

// 中略

// シート名
const SHEET_NAME = 'ユーザーリスト';

// ヘッダー情報
const SHEET_HEADER = [
    'ID' => 'integer',
    'NAME' => 'string',
    'BIRTHDAY' => 'yyyy/mm/dd',
];

$writer = new XLSXWriter();

// ヘッダー行の書き込み
$writer->writeSheetHeader(self::SHEET_NAME, self::SHEET_HEADER);

// データ行書き込み
foreach ($dataList as $data) {
    $writer->writeSheetRow(self::SHEET_NAME, $data);
}

// 出力
$filePath = './tmp/user_list.xlsx';
$writer->writeToFile($filePath);

// XLSXWriterはストレージに保存するしか方法がない?ようなので、Exportしたい時は出力後ファイル削除
if (file_exists($filePath)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($filePath));
    $readFile = readfile($filePath);
    if ($readFile) {
        unlink($filePath);
    }
}
exit;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「チーム共有機能・Views生成時の命名規則修正」LaravelDB.com

◇Laravel DB.com を初めて知った人はこちらから

1.Laravel DB.com ってなに?前回の紹介記事です!

2.Qiita記事LaravelDB.comの紹介 >>

3.次にLaravelDB.comを知った人は以下へどうぞ。

4.LaravelDB.comへログイン >>

◇NewUpdate[2020-02-25~]

1.Update:Views生成時フォルダ名の命名規則

以前と現在のLaravelDB.comのView生成時の命名規則は以下です。

記法 複/単
以前 キャメルケース 複数形 testDemos
現在 スネークケース 単数形 test_demo

※キャメルケースは、複合語をひと綴りとして、要素語の最初を大文字で書き表す記法。
※アンダースコア( _ )を区切記号として単語をつなげる記法。

何故、いま「Viewsの命名規則」を変えたのか?
Auth関連の処理を追加・変更しようとする際に「現在の記法(スネークケース)」でないと動作しないケースがあるため変更しました。

2.New:チーム共有機能(他のメンバーとER図を共有)

LaravelDB.comをリリース後に ”Feedbackで一番多かった” のが、「Team開発やメンターとのオンラインMTG用にER図を共有したい」でした。

「どうやったら?シンプルに実装でき、ユーザーが簡単に操作できるか?」の仕様検討が最も時間がかかりました。
数日間悩んだ結果、一つの方法を昨日考えつきました。仕様と実装方法が見えてきたらそこからは1日半程度で完成し、その翌日にはリリースしました。スピードは時間を有意義に使うための重要なスキルです。これで、オンライン上でテーブルの設計が共有でき、CRUDが簡単に生成できることでしょう。
また 追加機能・改修等があれば今後もアップデート記事を書いていきます。

【シェアData「送信側」】

テーブル設計をシェアする機能のことです。

1 [シェアData]作成


POINT:
この時点で共有データが作成されます。
見せたい相手にIDを渡しておいて、変更があれば「Create a [Share ID]」ボタンを押すと毎度データ更新されることを知っておきましょう! 「データ更新したので見てください!」って後から言えるってことですね。

2 [シェアData] IDをコピー

ここでコピーしたIDを相手に知らせます。

【シェアData 「受信側」】

1 [シェアData]読み込み

相手は送られてきた[シェアData]IDを貼り付けます。
こちらのIDを知っていれば誰でも読み込めます!見て欲しい人に渡しましょう!
※LOGIN(Googleアカウントで)しないと見れないことは認識しておいてください。

2.「Read」ボタンをクリックしてデータを受信表示しましょう。

3.シェアデータの複製が完了!!

受信側にデータが入りました!受信側もそのデータを活用できるようになります。
チーム・メンタリング等のケースでも利用可能です。

※受信後は「別名を付けて保存しておくと良いでしょう!」


LaravelDB.com解説ページ一覧

コード書かずに超スピード開発~(DEMO動画あり)~最新版『 Laravel DB.com 』

https://qiita.com/daisu_yamazaki/items/068595670bdc2b6fe3fc

LaravelDB.com 対応カラム一覧

https://qiita.com/daisu_yamazaki/items/92dc3cc599a264c3fb0f

LaravelDB.com テーブル命名ルール

https://qiita.com/daisu_yamazaki/items/1cb5987cc6d1008def82

LaravelDb.com integerの注意点

https://qiita.com/daisu_yamazaki/items/f2e6d58cfa20fa81fd54

LaravelDB.com Faker(テストデータ投入)

https://qiita.com/daisu_yamazaki/items/57669e8fa2c256d85c95

Twitter

LaravelDB.com

以上

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

NewUpdate!!「チーム共有機能・Views生成時の命名規則修正」LaravelDB.com

◇Laravel DB.com を初めて知った人はこちらから

1.Laravel DB.com ってなに?前回の紹介記事です!

2.Qiita記事LaravelDB.comの紹介 >>

3.次にLaravelDB.comを知った人は以下へどうぞ。

4.LaravelDB.comへログイン >>

◇NewUpdate[2020-02-25~]

1.Update:Views生成時フォルダ名の命名規則

以前と現在のLaravelDB.comのView生成時の命名規則は以下です。

記法 複/単
以前 キャメルケース 複数形 testDemos
現在 スネークケース 単数形 test_demo

※キャメルケースは、複合語をひと綴りとして、要素語の最初を大文字で書き表す記法。
※アンダースコア( _ )を区切記号として単語をつなげる記法。

何故、いま「Viewsの命名規則」を変えたのか?
Auth関連の処理を追加・変更しようとする際に「現在の記法(スネークケース)」でないと動作しないケースがあるため変更しました。

2.New:チーム共有機能(他のメンバーとER図を共有)

LaravelDB.comをリリース後に ”Feedbackで一番多かった” のが、「Team開発やメンターとのオンラインMTG用にER図を共有したい」でした。

「どうやったら?シンプルに実装でき、ユーザーが簡単に操作できるか?」の仕様検討が最も時間がかかりました。
数日間悩んだ結果、一つの方法を昨日考えつきました。仕様と実装方法が見えてきたらそこからは1日半程度で完成し、その翌日にはリリースしました。スピードは時間を有意義に使うための重要なスキルです。これで、オンライン上でテーブルの設計が共有でき、CRUDが簡単に生成できることでしょう。
また 追加機能・改修等があれば今後もアップデート記事を書いていきます。

【シェアData「送信側」】

テーブル設計をシェアする機能のことです。

1 [シェアData]作成


POINT:
この時点で共有データが作成されます。
見せたい相手にIDを渡しておいて、変更があれば「Create a [Share ID]」ボタンを押すと毎度データ更新されることを知っておきましょう! 「データ更新したので見てください!」って後から言えるってことですね。

2 [シェアData] IDをコピー

ここでコピーしたIDを相手に知らせます。

【シェアData 「受信側」】

1 [シェアData]読み込み

相手は送られてきた[シェアData]IDを貼り付けます。
こちらのIDを知っていれば誰でも読み込めます!見て欲しい人に渡しましょう!
※LOGIN(Googleアカウントで)しないと見れないことは認識しておいてください。

2.「Read」ボタンをクリックしてデータを受信表示しましょう。

3.シェアデータの複製が完了!!

受信側にデータが入りました!受信側もそのデータを活用できるようになります。
チーム・メンタリング等のケースでも利用可能です。

※受信後は「別名を付けて保存しておくと良いでしょう!」


LaravelDB.com解説ページ一覧

コード書かずに超スピード開発~(DEMO動画あり)~最新版『 Laravel DB.com 』

https://qiita.com/daisu_yamazaki/items/068595670bdc2b6fe3fc

LaravelDB.com 対応カラム一覧

https://qiita.com/daisu_yamazaki/items/92dc3cc599a264c3fb0f

LaravelDB.com テーブル命名ルール

https://qiita.com/daisu_yamazaki/items/1cb5987cc6d1008def82

LaravelDb.com integerの注意点

https://qiita.com/daisu_yamazaki/items/f2e6d58cfa20fa81fd54

LaravelDB.com Faker(テストデータ投入)

https://qiita.com/daisu_yamazaki/items/57669e8fa2c256d85c95

Twitter

LaravelDB.com

以上

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

Update「チーム共有機能・Views生成時の命名規則修正」LaravelDB.com

◇Laravel DB.com を初めて知った人はこちらから

1.Laravel DB.com ってなに?前回の紹介記事です!

2.Qiita記事LaravelDB.comの紹介 >>

3.次にLaravelDB.comを知った人は以下へどうぞ。

4.LaravelDB.comへログイン >>

◇NewUpdate[2020-02-25~]

1.Update:Views生成時フォルダ名の命名規則

以前と現在のLaravelDB.comのView生成時の命名規則は以下です。

記法 複/単
以前 キャメルケース 複数形 testDemos
現在 スネークケース 単数形 test_demo

※キャメルケースは、複合語をひと綴りとして、要素語の最初を大文字で書き表す記法。
※アンダースコア( _ )を区切記号として単語をつなげる記法。

何故、いま「Viewsの命名規則」を変えたのか?
Auth関連の処理を追加・変更しようとする際に「現在の記法(スネークケース)」でないと動作しないケースがあるため変更しました。

2.New:チーム共有機能(他のメンバーとER図を共有)

LaravelDB.comをリリース後に ”Feedbackで一番多かった” のが、「Team開発やメンターとのオンラインMTG用にER図を共有したい」でした。

「どうやったら?シンプルに実装でき、ユーザーが簡単に操作できるか?」の仕様検討が最も時間がかかりました。
数日間悩んだ結果、一つの方法を昨日考えつきました。仕様と実装方法が見えてきたらそこからは1日半程度で完成し、その翌日にはリリースしました。スピードは時間を有意義に使うための重要なスキルです。これで、オンライン上でテーブルの設計が共有でき、CRUDが簡単に生成できることでしょう。
また 追加機能・改修等があれば今後もアップデート記事を書いていきます。

【シェアData「送信側」】

テーブル設計をシェアする機能のことです。

1 [シェアData]作成


POINT:
この時点で共有データが作成されます。
見せたい相手にIDを渡しておいて、変更があれば「Create a [Share ID]」ボタンを押すと毎度データ更新されることを知っておきましょう! 「データ更新したので見てください!」って後から言えるってことですね。

2 [シェアData] IDをコピー

ここでコピーしたIDを相手に知らせます。

【シェアData 「受信側」】

1 [シェアData]読み込み

相手は送られてきた[シェアData]IDを貼り付けます。
こちらのIDを知っていれば誰でも読み込めます!見て欲しい人に渡しましょう!
※LOGIN(Googleアカウントで)しないと見れないことは認識しておいてください。

2.「Read」ボタンをクリックしてデータを受信表示しましょう。

3.シェアデータの複製が完了!!

受信側にデータが入りました!受信側もそのデータを活用できるようになります。
チーム・メンタリング等のケースでも利用可能です。

※受信後は「別名を付けて保存しておくと良いでしょう!」


LaravelDB.com解説ページ一覧

コード書かずに超スピード開発~(DEMO動画あり)~最新版『 Laravel DB.com 』

https://qiita.com/daisu_yamazaki/items/068595670bdc2b6fe3fc

LaravelDB.com 対応カラム一覧

https://qiita.com/daisu_yamazaki/items/92dc3cc599a264c3fb0f

LaravelDB.com テーブル命名ルール

https://qiita.com/daisu_yamazaki/items/1cb5987cc6d1008def82

LaravelDb.com integerの注意点

https://qiita.com/daisu_yamazaki/items/f2e6d58cfa20fa81fd54

LaravelDB.com Faker(テストデータ投入)

https://qiita.com/daisu_yamazaki/items/57669e8fa2c256d85c95

Twitter

LaravelDB.com

以上

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

Laravel で Fat Model を防ぐ 5 つの Tips

この記事について

前回 Fat Controller について同様の記事を書きました。
Laravel で Fat Controller を防ぐ 5 つの Tips - Qiita

引き続いて Fat Model 編です。Fat Model に関しては、Laravel を使っているとわりとそうなる傾向にあるなぁという印象を持っているので、どうすれば解消できるのか悩んでいる方は多いんじゃないかと想像しています。それらを防ぐために使えるテクニックというか、Tips を5つばかり紹介したいと思います。

はじめに

Fat Model とは?

簡潔にいえば、行数が多く(個人的には Model だと 200 行超えると Fat 感を感じます、みなさんはいかがでしょうか)、行数が多いがために処理の流れを追うことが難しく、しばしば不具合の原因になるクラスのことをいうのだと思います。ただ行数が多い、というだけでなく、複数のクライアント(呼び出し側)で処理が重複していたり、メソッドの中で条件分岐が発生したりして、ひとつの変更が他の部分に及ぼす影響を検知しづらい状態にあるクラスを想定しています。

巨大なクラスであっても、そこに含まれるデータや関数が高凝集・低結合であれば問題ないと考えます。大事なのは「不具合の原因になる」ということであって、すべての巨大なクラスが悪である、ということではないと思っています。

Fat Model の問題点

まず前提として、この記事では Model は Controller と View 以外のものを指しています。前述の Policy や FormRequest なども Model の一種と捉えています。が、ここでは主に Illuminate\Database\Eloquent\Model (以下 Eloquent Model)を継承したクラスを対象にします。おそらく、最も Fat Model になりやすいのはここだと思うからです。

  • 大量のメソッドがあり、ひとつのプロパティを操作するメソッドが複数(しかも多く)存在するため、データモデルの変更に追従するのが難しい。それによって、データモデルを変更すると、予期せぬ不具合が発生することがある
  • ひとつひとつのメソッドが長く、処理の流れを追うことが難しい。それによって、局所的な変更が処理全体に影響することがあり、予期せぬ不具合が発生することがある
  • Model の複数のバリエーションを同一クラス内で扱っており、バリエーションの違いを判定するための条件分岐があちこちに発生する。それによって、バリエーションが増えた際にコードの変更が漏れ、予期せぬ不具合が発生することがある

Fat Model になる主な要因として、テーブルと対になった Eloquent Model のみが存在し、基底クラスの制約上リレーションシップ、クエリスコープ、アクセサ・ミューテータが一緒くたになっているため、そこから処理を委譲しにくい、というのがあるんじゃないかと思います。個人的には、適切に処理がカプセル化できているのであれば、Fat Model は( Fat Controller に比べれば)悪いことではないと思っていて、逆に Eloquent Model が Slim になっている場合、本来カプセル化しておくべき処理が外部に漏れている可能性があって、そちらのほうがずっと危険だと考えます。

Laravel と同じく Active Record パターンを採用している Ruby on Rails でも同様の問題が取り上げられており、普及した時期が Laravel よりも早いため、議論の過程は参考になるのではないかと思います。

Railsのファットモデル問題に対処する前に読んでほしい記事 - Qiita

Fat Model を防ぐ 5 つの Tips

  1. リクエストのデータを処理する関数は FormRequest に書く
  2. レスポンスのデータを処理する関数は ViewModel あるいは Resource に書く
  3. 複雑なロジックを別のクラスに委譲する
  4. Repository パターンを導入する
  5. コンテキストやバリエーションごとに継承したクラスを用意する

1. リクエストのデータを処理する関数は FormRequest に書く

Fat Controller 編と同じです。

モデルにあるメソッドで Request を受け取ってゴニョゴニョしている処理がある場合は、FormRequest へ移動させましょう。

単純な例は Fat Controller 編の例を見ていただくとして、複雑なクエリビルディングがある場合の例を書いておきます。

// Model
public function scopeMatched(Builder $builder, Request $request) {
    $keyword = $request->keyword;
    $containsCompleted = $request->contains_completed;
    // 他にもたくさんのパラメータがある
    $orderBy = $request->order_by;

    return $builder
        ->where('keyword', 'LIKE', "%{$keyword}%")
        ->when(!$containsCompleted, function ($builder) {
            $builder->whereIn('status', ['todo', 'doing']);
        })
        // 他にもたくさんのメソッドチェーンがある
        ->orderBy($orderBy);
}

入力パラメータが複雑になる場合は、Scope クラスを経由して組み立てることができます。

公式ドキュメント ( https://laravel.com/docs/6.x/eloquent#global-scopes )では Scope の使い方は boot メソッドで addGlobalScope を使う例しか載っていませんが、別にこれは addGlobalScope 経由でしか使えないというわけではなく、アドホックなクエリビルディングでも以下のように使うことができます。

// FormRequest
public function filterScope() {
    return new MatchedScope($this->all());
}

// Scope
public function apply(Builder $builder, Model $model) {
    $builder
        ->where('keyword', 'LIKE', "%{$this->keyword}%")
        ->when(!$this->containsCompleted, function ($builder) {
            $builder->whereIn('status', ['todo', 'doing']);
        })
        ->orderBy($this->orderBy);
}

// Controller
$tasks = Task::matched($request->filterScope())->get();

// Model
public function scopeMatched(Builder $builder, MatchedScope $scope) {
    $scope->apply($builder, $this);
    return $builder;
}

2. レスポンスのデータを処理する関数は ViewModel あるいは Resource に書く

こちらも Fat Controller 編と同じです。

とはいえ、Eloquent に備わったアクセサ/ミューテータを活用したいなら、Model に書かざるを得ません。ここは利便性とのトレードオフなのかな、とも思います。

アクセサに複雑な処理あるいは重複した処理があれば、それを別クラスにするのはおすすめです。

以下では、アクセサを利用せず、Resource 側で整形するパターンを紹介します。

例として、日付と時刻を別々に持っており、日付・時刻ともにオプショナル、時刻のありなしでフォーマットが異なる、というルールがあったとき、

// Model
public function getReservedAtAttribute(): string {
    if (empty($this->attributes['reserved_at_date'])) {
        return '';
    }
    if (empty($this->attributes['reserved_at_time'])) {
        return Carbon::parse($this->attributes['reserved_at_date'])->format('Y-m-d');
    }
    $dt = sprintf('%s %s', $this->attributes['reserved_at_date'], $this->attributes['reserved_at_time']);
    return Carbon::parse($dt)->format('Y-m-d H:i');
}

この処理をまるっと別のクラスに移し、Resource クラスで変換するように変更してみます。

// OptionalTimeDateTimeFormatter
public function __construct(string $date, string $time) {
    // snip
}
public function format(): string {
    if (empty($this->date)) {
        return '';
    }
    if (empty($this->time)) {
        return Carbon::parse($this->date)->format('Y-m-d');
    }
    $dt = sprintf('%s %s', $this->date, $this->time);
    return Carbon::parse($dt)->format('Y-m-d H:i');
}

// Resource
public function toArray($request): array {
    $dateTimeFormatter = new OptionalTimeDateTimeFormatter(
        $this->reserved_at_date,
        $this->reserved_at_time
    );
    return [
        'reserved_at' => $dateTimeFormatter->format(),
        // snip
    ];
}

トレイトにするのでもいいかもしれません。

個人的には、Resource は便利な半面ややトリッキーな部分も多いので(詳細は省きますが)、あまりこのクラスにロジックを書かないほうがいいと思っていますが、委譲するクラスの生成と単一の API 呼び出しだけであれば、許容範囲かな、とは思います。

3. 複雑なロジックを別のクラスに委譲する

2 のケースとやることはほとんど同じです。

例として、TODO アプリケーションで見積もり時間と実績時間から進捗率を算出して返すようなメソッドがあるとします。単純な四則演算ではなく、見積もりは 1w とか 3h とかの形式で設定でき、実績時間は 00:00 のように分までの時間を足し合わせていく方式とします。

// Task Model
public function getProgressRateAttribute(): ?float {
    if ($this->estimate === null) {
        return null;
    }
    $suffixCoefficientMap = ['w' => 5 * 24 * 60, 'd' => 24 * 60, 'h' => 60];
    $matches = [];
    if (!preg_match("/\A(\d+)([wdh]{1})\z/", $this->estimate, $matches)) {
        throw new \LogicException('estimate invalid format.')
    }
    list(, $value, $suffix) = $matches;
    $estimate = $matches[1] * $suffixCoefficientMap[$matches[2]];

    // このあと実績時間の計算が続く...

    return $estimate / $sumWorkTimes;
}

まぁまぁ長くなりますね(途中で力尽きました)。

これを、

// Task Model
public function getProgressRateAttribute(): ?float {
    if ($this->estimate === null) {
        return null;
    }

    $rate = new ProgressRate($this->estimate, $this->work_times);
    return $rate->value();
}

みたいにすれば、1/4 くらいになります。メソッドが長くなってきたらプライベートメソッドに切り出したりすると思いますが、 $this が使われてなかったり、特定のプロパティしか使ってないようなら別のクラスに切り出しやすいです。

4. Repository パターンを導入する

Repository パターンをご存じない方はググってみてください。本記事では Martin Fowler さんの定義のみ載せておきますが、アーキテクチャ全体に関わることなので、導入に際しては十分に検討したほうがいいでしょう。

https://martinfowler.com/eaaCatalog/repository.html

Eloquent が肥大化する要因のひとつに、コレクション操作と単一のインスタンス操作がミックスされていることがあろうかと思います。

// コレクション
$tasks = Task::matched($request->filters())->get();

// 単一インスタンス
$task->doSomething();

上記2つのメソッドはいずれも Model 側に存在するわけですが、Repository クラスを導入すれば、コレクション操作を分離することができます。

// コレクション
$tasks = $taskRepository->find(new MatchedScope($request->filters()));

// 単一インスタンス
$task->doSomething();

上の例では、Criteria の代わりに Scope を使っています。本来 Repository は永続化層の変更に柔軟に対応するため、というのがあるので、Criteria を汎用的なつくりにしてクエリビルダーへの依存性をなくしておいたほうが、本来の使い方に則しているかなと思いますが、コレクション操作を分離するだけであれば不要と考えます。

5. コンテキストやバリエーションごとに継承したクラスを用意する

ある特定のコンテキストだったり、ある状態にもとづいて別のバリエーションが存在するようなケースで、クラスを分けることを検討してもいいと思います。

例として(あまり適切な例じゃないかもしれないですが)、タスクが「タスク」と「リマインダー」に分かれているものの、データベースには同じテーブルに入っていて、task_type みたいなフィールドの値で分類している、とします。

そうしたケースで、「リマインダー」だけが実行可能なユースケースがあり、そうしたユースケースで使われるメソッドが「タスク」クラスに混在しているとき、「リマインダー」用のメソッドを分離することができます。

// Task
class Task extends Model {
    public static function boot() {
        parent::boot();
        static::addGlobalScope('task', function (Builder $builder) {
            $builder->where('task_type', 'task');
        })
    }
}

// Reminder
class Reminder extends Model {
    protected $table = 'tasks';

    public static function boot() {
        parent::boot();
        static::addGlobalScope('reminder', function (Builder $builder) {
            $builder->where('task_type', 'reminder');
        })
    }  
}

// Controller
// task_type=task の ID が指定された場合は 404 になる
public function snooze(Reminder $reminder, SnoozeReminderRequest $request) {
    $reminder->snooze($request->time); // snooze は Reminder だけの機能
    return response()->json(null, Response::HTTP_NO_CONTENT);
}

Task クラスのメソッドを使う場合は、 extends Task でもいいでしょう。

ただし、適切に分離せずオープンクローズド原則に違反するようなつくりになってしまうと、却って不具合の原因になることもあるので、そのコンテキストあるいはバリエーションが完全に独立しているか、分離すべきメソッドのみ分離しているか、など、慎重に検討してください。

おわりに

いかがでしたでしょうか。例をひねりだすのに苦労して、もしかしたら不適切な例もあるかもしれませんが、Fat Model を解消したいとお悩みの方の一助になれば幸いです。

他にも Fat Model 解消法をお持ちの方、コメント欄にて教えていただけると大変助かります :bow:

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

【小ネタ】引数のコンマ区切りを卒業し、関数呼び出しの可読性を高めよう

別にphpに限った話ではないけど。
eval関数の使える言語なら大抵似たようなことができると思う。
evalが使えなくても文字列演算をすれば代用可能。

1.執筆の動機

例えばPostgreSQLでは、文字列から部分文字列を取り出す関数として、
substring('string' from 2 for 3);
みたいのがある。このコードでは1オリジン2文字目から3文字だけ取り出すから、
結果は'tri'となる。

引数がコンマ,ではなく、fromとかforで区切られている。
そのおかげで、
substring('string', 2, 3);
のようなコードと比べ、可読性が向上しているとは言えないだろうか。

是非このような仕組みをほかの言語でも可能にしたいと思ったのが、本稿執筆のきっかけ。

2.実現方法

結論から言うと、引数全体を一つの文字列とし、それを連想配列に変換しちゃえばいい。

例: 'st r_ing'の2文字目から5文字取り出したい場合

まず、次のような関数で文字列を連想配列に変換する。

php
function arg2assoc(string $str)
{
    $str = str_replace("__", "_", preg_replace("/([^_]|\A)_([^_]|\z)/", "$1 $2", rtrim(preg_replace("/[ \t\r\n]*([^ \t\r\n]+)[ \t\r\n]*([^ \t\r\n]*)[ \t\r\n]*/", "'$1'=>$2,", $str), ",")));
    return eval("return [$str];");
}

以上のようにarg2assoc関数を書いたら、
var_dump(arg2assoc("str 'st_r__ing' from 2 for 5"));
を実行すると次のようになる。

array(3) {
  ["str"]=>
  string(8) "st r_ing"
  ["from"]=>
  int(2)
  ["for"]=>
  int(5)
}

後は、この連想配列を関数内部で利用すればよい。
例えば
substring('st r_ing', 2, 5);
substr("'st_r__ing' from 2 for 5");
のように書けるようにしたい場合は、次のようにすればよい。

php
function substr(string $argstr)
{
    $assoc = arg2assoc("str ".$argstr);
    return substring($assoc['str'], $assoc['from'], $assoc['for']);
}

3.仕様説明

arg2assoc関数をもう少し丁寧に書くと次のようになる。

php
function arg2assoc(string $str)
{
    //"str 'st_r__ing' from 2 for 5"
    //(ホワイトスペースを引数に含める場合、「_」で置き換える)
    //(「_」を含めたい場合は「__」と2個連続で。)

    $str = preg_replace("/[ \t\r\n]*([^ \t\r\n]+)[ \t\r\n]*([^ \t\r\n]*)[ \t\r\n]*/", "'$1'=>$2,", $str);
    //"'str'=>'st_r__ing','from'=>2,'for'=>5,"

    $str = rtrim($str, ",");
    //"'str'=>'st_r__ing','from'=>2,'for'=>5"

    $str = preg_replace("/([^_]|\A)_([^_]|\z)/", "$1 $2", $str);
    $str = str_replace("__", "_", $str);
    //"'str'=>'st r_ing','from'=>2,'for'=>5"

    return eval("return [$str];");
}

'st r_ing''st_r__ing'に置き換えて渡すことの必要性について説明する。
正規表現では原理的にカッコ言語を受理できないため、
例え引用符でくくってあったとしても、配列の区切りの空白と、文字列としての空白の区別をつけることができないのである。
そこで、文字列としての空白は_で置き換えて渡す仕様とした。
また、この仕様により、空白の置き換えとしての_と、文字列としての_の区別がつかない問題が新たに発生した。
そこで文字列としての_は、自身を2連続した__で表現することとした。

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

PHPとjavascriptで営業日カレンダー

phpとjavascriptで営業カレンダーを作る

完成した表示
スクリーンショット 2020-02-27 9.32.01.png
仕様
・休みは赤色にする
・定休日:祝日と日曜、木曜
・アイコンクリックで表示月変更

設計
・PHPでカレンダーを構築する配列をJSONで出力
・ajaxで受け取り出力する。

1.祝日配列のつくる

内閣府のページに祝日のcsvがあるので、そこからダウンロードする。
https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html
※csvはUTF-8に変換して保存。

calendar.php
function get_public_holidays(){
    $path = "./syukujitsu.csv";
    $csv = new SplFileObject($path);
    $csv->setFlags(SplFileObject::READ_CSV);
    $holidays = [];
    foreach ($csv as $key => $line){
        $holidays[$line[0]] = $line[1];
    }
    return $holidays;
}

2.カレンダーを構築する配列をつくる

calendar.php
function make_date_list($year,$month){

    $regular_holiday = [0,4]; //定休日:曜日
    $public_holidays = get_public_holidays(); //祝日

    $target = new \DateTime("{$year}-{$month}-01");
    $date = [];
    for($i = 0; $i < intval($target->format('t')); $i++){

        if($i > 0) $target->modify('+1day');
        //祝日
        if(isset($public_holidays[$target->format('Y/n/d')])){
            $holiday = true;
        //定休日
        }else if(in_array($target->format('w'),$regular_holiday)){
            $holiday = true;
        }else{
            $holiday = false;
        }

        $dates[$i] = [
            'day' => $target->format('d'),
            'week' => $target->format('w'),
            'holiday' => $holiday,
        ];

    }

    /*カレンダーの空欄箇所*/
    $first_date = current($dates);
    $end_date = end($dates);

    $damy_num = intval($first_date['week']);
    if($damy_num > 0){
        $damy = [];        
        for($i = $damy_num - 1;0 <= $i;$i--){
            array_unshift($dates,['week'=>$i,'day'=>'&nbsp;']);
        }    
    }

    $damy_num = intval($end_date['week']);
    if($damy_num < 6){
        $damy = [];        
        for($i = $damy_num; $i < 6;$i++){
            array_push($dates,['week'=>$i,'day'=>'&nbsp;']);
        }
    }

    return $dates;
}

3.リクエストがよる出力

calendar.php
if(isset($_GET['y']) and $_GET['y']){
    $year = $_GET['y'];
}else{
    $year = date('Y');
}

if(isset($_GET['m']) and $_GET['m']){
    $month = $_GET['m'];
}else{
    $month = date('m');
}

$dates = make_date_list($year,$month);

echo json_encode($dates);

4.javascriptでのカレンダー構築

2ヶ月先までの表示にしています。

calendar.js
var nowDate;
var year;
var month;

$(function(){
    nowDate = new Date();
    year = nowDate.getFullYear(); // 年
    month = nowDate.getMonth() + 1; // 月

    MakeCalendar();
});

function prev(){
    if(month === 1){
        month = 12;
        year--;
    }else{
        month--;
    }
    MakeCalendar();

}

function next(){
    if(month === 12){
        month = 1;
        year++;
    }else{
        month++;
    }
    MakeCalendar();
}

function MakeCalendar(){
    $.ajax({
        url: 'calender.php?y='+year+'&m='+month,
        dataType: 'json',
        timeout: 10000,  
        success: function(json) {

            var c = '<div class="cal"><div class="cal_header">';
            if(nowDate.getFullYear() <= year && (nowDate.getMonth() + 1) < month ){
                c += '<a href="" onClick="prev();return false;" class="cal_prev"><img src="./images/cal_prev.png" alt="前の月" width="19"></a>';
            }else{}

            if(nowDate.getFullYear() >= year && (nowDate.getMonth() + 3) > month ){
                c += '<a href="" onClick="next();return false;" class="cal_next"><img src="./images/cal_next.png" alt="次の月" width="19"></a>'
            }else{}

            c += '<strong>'+year+''+month+'月</strong>';
            c += '</div>';
            c += '<div class="cal_body">';
            c += '<table class="table_cal">';
            c += '<thead><tr><th>日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th></tr></thead>';

            jQuery.each(json, function(i, date) {

                if(date.week === '0'){
                    c += '<tr>';
                }else{}
                if(date.holiday === true){
                    c += '<td><span class="closed">'+date.day+'</span></td>';
                }else{
                    c += '<td>'+date.day+'</td>';
                }

                if(date.week === '6'){
                    c += '</tr>';
                }else{}

            });

            c += '</tbody></table></div></div>';

            $('#calendar').html(c);

        },
         error: function(xhr, textStatus, error) {
            alert('ERR');
        }
    });

}

4.表示

表示する箇所にタグとCSSを構築して完成。

sample.html
<div id="calendar"></div>

sample.css
.cal{background:#fff;box-shadow:2px 2px 4px rgba(0,0,0,0.2);}
.cal_header{padding:15px 13px;border-bottom:1px solid #ddd;}
.cal_header a,.cal_header span{display:block;}
.cal_header strong{display:block;text-align:center;font-size:1.3rem;color:#005ca2;line-height:19px;}
.cal_prev{float:left;cursor:pointer;}
.cal_next{float:right;cursor:pointer;}
.cal_body{padding:10px 15px;text-align:center;}

.table_cal{width:100%;border-collapse:collapse;}
.table_cal th,.table_cal td{font-size:1.1rem;text-align:center;height:30px;min-width:31px;}

.today{font-weight:bold;text-decoration:underline;}
.closed{display:inline-block;width:22px;height:22px;background:#eb6e8f;color:#fff;border-radius:50%;line-height:23px;}

.cal_bottom{font-size:1.1rem;margin:10px 0 15px;}
.cal_bottom span{vertical-align:middle;margin-right:4px;}

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