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

WEBページに「Googleアカウントでログイン」を実装する

目次

  • はじめに
  • 目標
  • クライアントIDを取得する
  • クライアントサイドのコード(JavaScript)
  • サーバサイドのコード(PHP)
  • 実行してみる
  • お世話になったサイト

はじめに

WEBアプリを作っていて、「Googleアカウントでログイン」が実装できたので備忘録として残しておく。
実装にあたっては、Googleの公式ドキュメントを大いに参考にしました。動画も一緒に載っていて、日本人が解説しています。Japanese Englishですごく聞き取りやすくて助かりました。皮肉じゃないです。日本語の方がうれしいんですけどね
PHPとJSを使って実装しています。サーバサイドの言語は自分はPHPを使いましたが、公式ドキュメントにはNode.jsとJavaとPythonのコードも載ってました。自分はPHPしか試していません。

必要なもの

  • テキストエディタ
  • サーバ(ローカル環境でも可、試してないから知らんけど)
  • ブラウザ
  • Composer(PHPを使ったので必要になった)
  • PHP(Composerを使うので)

目標

GoogleアカウントでログインされたユーザのGoogleアカウントのプロフィール情報を取得し、ログイン状態を付与する、というプログラムを書きます。作成するファイルは、以下の通りです。

  • Googleアカウントでログインするためのページを表示させるファイル(HTML)
  • ユーザのプロフィール情報を受け取るための処理をするファイル(PHP)
  • ログイン成功後に遷移するページさせるファイル(PHP)

表示するページのファイルは2つ、バックエンドの処理を行うファイルが1つ、計3つのファイルを作成します。ここでは最低限しか作っていないので、ユーザのプロフィールを取得していろいろとやりたいことがある場合はこのほかにいろいろ処理を足すことになります。

クライアントIDを取得する

API使用の際に定番の、クライアントID取得がまずは必要です。GCPから、プロジェクトの作成を行います。
プロジェクトのホームから、APIとサービス>認証情報 を選択します。
image.png

画面左上の「認証情報を作成」を選択します。
image.png

作成するのは「OAuthクライアントID」です。
image.png

アプリケーションの種類を「ウェブアプリケーション」と選択し、入力事項を埋めます。
image.png

作成が完了するとクライアントIDが発行されます。あとで必要になります。

クライアントサイドのコード

ログインさせるページ

HTMLとJSのみで実装します。クライアントIDを認識させ、Google Platform Libraryを読み込みます。HTMLファイルのheadタグ内に以下のコードをコピペします。

<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="<用意したクライアントID>">

bodyタグ内にはGoogleアカウントでログインボタンを設置します。これもGoogleが用意してくれています。

<div class="g-signin2" data-onsuccess="onSignIn"></div>

getBasicProfile()メソッドを使うことで、ログインしたユーザのプロフィール情報を取得できます。scriptタグ内に以下
を記述します。

function onSignIn(googleUser) {
  var profile = googleUser.getBasicProfile();
  console.log('ID: ' + profile.getId()); // Do not send to your backend! Use an ID token instead.
  console.log('Name: ' + profile.getName());
  console.log('Image URL: ' + profile.getImageUrl());
  console.log('Email: ' + profile.getEmail()); // This is null if the 'email' scope is not present.
}

コンソールにユーザのプロフィールが表示されているかと思います。これでユーザの識別がクライアント側でできたことになります。ここで取得しているプロフィール情報はクライアント側でしか持っていません。このGoogleアカウントのプロフィール情報を自サイトにログインするのに使うために、サーバに送信する必要があります。しかし、ここで注意しなければならないことがあります。

  • gmailのアドレスを個人の識別に使ってはいけない。代わりにGoogle IDまたはsub(後述)を使う。
  • なりすましのリスクがあるため、getId()で得られたGoogle IDを生でサーバに送信してはいけない。代わりにIDトークンを送信する。

ユーザの識別にはIDを使え、だがIDは直接送らず、IDトークンを送れ、ってことです。IDトークンはただの文字列です。サーバ側でそのトークンをもとにユーザのプロフィール情報を取得することが可能です。

ってなわけでIDトークンをサーバにPOST送信します。
まずIDトークンを取得します。さっきコピペした関数の中身を変えます。POST送信ができれば別にこの書き方にこだわる必要はありません。個人的にjQueryのajax()のほうがわかりやすくて好きなので自分はそっちで書きましたがうまくいきました。

function onSignIn(googleUser) {
  var id_token = googleUser.getAuthResponse().id_token;
  var xhr = new XMLHttpRequest();
  xhr.open('POST', 'https://yourbackend.example.com/tokensignin');
  xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  xhr.onload = function() {
    console.log('Signed in as: ' + xhr.responseText);
  };
  xhr.send('idtoken=' + id_token);
}

サーバサイドのコード

POST送信されたIDトークンを受け取り、トークンを照合します。トークンの称号にはGoogle API Client Libraryが必要です。Composerを使ってインストールします。Composerの使い方までは解説しません。自分もよく知らないからです。

composer require google/apiclient

POSTでIDトークンを受け取り、照合しています。

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

$id_token = filter_input(INPUT_POST, 'id_token');
define('CLIENT_ID', '691530918857-qpu8h3bmt1fmd3onouvlu7dgcu46tlmn.apps.googleusercontent.com');

$client = new Google_Client(['client_id' => CLIENT_ID]); 
$payload = $client->verifyIdToken($id_token);
if ($payload) {
  $userid = $payload['sub'];
}

照合に成功したら、$payloadにプロフィール情報が入ります。$payloadの中身は以下のようになってます。

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

ユーザの識別をするためのIDは$payload['sub']の値です。gmailのユーザ名じゃないんや~?

このIDを使ってユーザの識別ができたらセッション変数でも使ってログイン状態を付与し、ログイン後のページに遷移します。

実行してみる

login.html
<html>
<head>
    <script src="https://apis.google.com/js/platform.js" async defer></script>
    <meta name="google-signin-client_id" content="<用意したクライアントID>">
</head>
<body>
    <div class="g-signin2" data-onsuccess="onSignIn"></div>
    <script>
        function onSignIn(googleUser) {
            var id_token = googleUser.getAuthResponse().id_token;
            var xhr = new XMLHttpRequest();
            xhr.open('POST', 'test.php');
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.onload = function() {
                console.log('Signed in as: ' + xhr.responseText);
            };
            xhr.send('idtoken=' + id_token);
            window.location.href = 'index.php';
        }
    </script>
</body>
</html>

test.php
<?php
session_start();
require_once 'vendor/autoload.php';

$id_token = filter_input(INPUT_POST, 'id_token');
define('CLIENT_ID', '<用意したクライアントID>');

$client = new Google_Client(['client_id' => CLIENT_ID]); 
$payload = $client->verifyIdToken($id_token);
if ($payload) {
    $userid = $payload['sub'];
}

//DBとのやりとりする

$_SESSION['login'] = true;
exit;
index.php
<?php
session_start();
if(!$_SESSION['login']){
    header('Location: login.html');
}
?>
<html>
<body>
    ログイン成功!    
</body>
</html>

login.phpにポツンと置かれたログインボタンからGoogleアカウントを選択してログインすれば、いつのまにかinndex.phpに飛んでいてくれるはず。

お世話になったサイト

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

PHPに触れて5か月の学生がPHPerKaigi2020へ行ってみたときの話

はじめに

 ある日気ままにネットサーフィンをしていたところ、こんな記事を見つけました。
あなたが今年PHPerKaigi 2020に参加しなければいけない理由
ふむふむ、2/9~11にかけてPHPerが一堂に集まる大きなイベントがあるのか~
予定は空いているな、で値段は...えっ最低でも参加料4800円か、少し料金は高いけど暇だし行ってみるか
ということでイベントへ参加することにしました。

そもそもPHPerKaigiとは

PHPerKaigi2020公式ホームページより

PHPerKaigi(ペチパーカイギ)は、PHPer、つまり、現在PHPを使用している方、過去にPHPを使用していた方、これからPHPを使いたいと思っている方、そしてPHPが大好きな方たちが、技術的なノウハウとPHP愛を共有するためのイベントです。

つまりPHPが好きだったり、興味を持っている人ならだれでもオッケーだそう。

行ってみての感想

 ここでは自分がPHPerKaigi2020に参加して、印象に残ったこと、良いなと感じたところ、ここはどうなのかなと感じたところを挙げていきます。

印象に残ったこと

参加者の数
会場内にはPHPerKaigi2020のスタッフと参加者合わせてざっくり300~400人くらいの人がいて、PHPが好きな人はこんなに多いのか!とまず驚きました。

セクション
もっと気軽にOSSにPRを出そう!(DQNEOさん)

  • フレームワークなどの製作者は自分の関心の外の部分は意外と適当だったり、間違いがあったりする⇒既存のフレームワークやパッケージを修正するチャンスは結構ある
  • 英語のスペルミスレベルの簡単な修正もある⇒専門的な知識がなくてもできる
  • PRに励むことで、自分の知識の横展開(応用)ができる

結論:みんな怖がらずにPR(pull Request)を出そう!

このセクションはあまりPHPについて詳しくない自分にとってもわかりやすかったし、何より聞いた後、俺もPR出してみようかな、というやる気がわきました。

イベント内の様々な仕掛け
 参加者が自分のツイッターのアカウント情報がが記載されたデュ〇ルマスターズを彷彿とさせるカードを持ち、ほかの参加者にそれを渡し合っていたのが面白かったです。(しかも一人一人効果まで書かれていたのがこだわりを感じました。)
 そのほかにも様々な仕掛けがあり、製作者の遊び心が垣間見えて面白かったです。

PHPKaigi2020の良いなと思ったところ

 自分が良いなと思ったところは、会場のいたるところに遊び心がある仕掛けがあり、文化祭みたいで楽しかったところです。またボードゲームコーナーがあるなどぼっちで行った自分でも他の参加者と交流できる機会があるのも良かったです。

PHPKaigi2020のここはどうなのかなと思ったところ

 料金設定が三日間参加で4800円からしかないのが微妙だな、と感じました。なぜなら、もし一日参加券みたいなものを作り、もう少し安く参加できる料金コースを設定すれば、PHPのカンファレンスに今まで参加したことがない層の人がより多く参加し、それによりPHPのコミュニティーのより一層の発展が見込めるからです。
(会場の都合・運営側の都合・スポンサー側の都合を全く理解していない立場からの発言です.)

おわりに

 初めてのPHPerKaigiは自分の予想より楽しかったです。今回の参加で、PHPのコミュニティーとしての面白さを感じることができたので、次は東京以外で行われるPHPerKaigiにも参加しようかなと思います。
運営の方々は三日間お疲れさまでした!

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

PHPerKaigi2020 スライドまとめ

PHPerKaigiとは?

PHPerによるPHPerのためのお祭りです。
以下、敬称略。表示順は採択Proposal順です。

2020/02/09

Deep Module in PHP

itosho @itosho
https://speakerdeck.com/itosho525/deep-module-in-php

PHP で JVM を実装して、 HelloWorld を出力してみる

めもりー @m3m0r7
https://speakerdeck.com/memory1994/php-de-jvm-woshi-zhuang-site-helloworld-wochu-li-sitemiru

マルチパラダイムモデリング 〜異なるモデリングパラダイムから見るモデリングの勘所〜

a-suenami @a_suenami
https://speakerdeck.com/a_suenami/marutiparadaimumoderingu-yi-narumoderinguparadaimukarajian-rumoderingufalsekan-suo-number-phperkaigi

PHPのアノテーションの仕組みとメリット・デメリット

山岡広幸 @hiro_y
https://speakerdeck.com/hiro_y/about-php-annotations

Inside SWOOLE:非同期処理はどのようにして動くのか

小山哲志 @koyhoge
https://speakerdeck.com/koyhoge/inside-swoole

2020/02/10

E2Eテストに向き合う

岸田健一郎 @sizuhiko
https://speakerdeck.com/sizuhiko/phperkaigi2020

CakePHPの進化から読み解く、PHPフレームワークの"今"っぽさ

きんじょうひでき @o0h_

記事執筆時点では資料を見つけられず。

PHPでつくるインタプリタ入門

清水 陽一郎 @budougumi0617
https://speakerdeck.com/budougumi0617/introduction-interpreter-by-php

PHPとEventSauceで始めるイベントソーシングアプリケーション

中榮健二 @n_1215
https://speakerdeck.com/n1215/phptoeventsaucedeshi-meruibentososinguapurikesiyon

エキサイトの大改造を大解剖!

齋藤 匠 @takumi_saitoh

記事執筆時点では資料を見つけられず。

レンサバけもの道

uzulla @uzulla
https://speakerdeck.com/uzulla/rensabakemofalsedao

磯野ー、MySQLのロック競合を表示しようぜー

yoku0825 @yoku0825
https://speakerdeck.com/yoku0825/ji-ye-mysqlfalserotukujing-he-wobiao-shi-siyouze

forteeに脆弱性診断をかけてみた

cakephper @cakephper

記事執筆時点では資料を見つけられず。

もっと気軽にOSSにPRを出そう!

DQNEO @DQNEO
https://speakerdeck.com/dqneo/lets-make-a-pr-to-oss-more-easily

AWS Lambda にCustom RuntimeでPHPを導入したシステムに改修を加えてUT導入まで行った話

しろぐちゆうま @yu_mashirou
https://speakerdeck.com/mashirou1234/aws-lambda-nicustom-runtimete-phpwodao-ru-sitasisutemunigai-xiu-wojia-ete-utdao-ru-matexing-tutahua

ぼくのかんがえたさいつよQueryBuilder

若葉 章 @effy_staffs
https://speakerdeck.com/ickx/phperkaigi2020-bokufalsekangaetasaituyoquerybuilder

知らないWebアプリケーションの開発に途中からJOINしたとき、どこから切り込むか?

小山健一郎 @k1LoW
https://speakerdeck.com/k1low/phperkaigi-2020

静的解析の育て方

杉山 祐一 @oogFranz
https://speakerdeck.com/oogfranz/how-to-make-your-static-analysis-strong

カンファレンス初心者が全国行脚を始め、登壇するまで

くろ/gattoman @gattoman

記事執筆時点では資料を見つけられず。

Laravelから始めるテスト駆動開発

Tsukahara @AkitoTsukahara
https://www.slideshare.net/ssuserc74359/akitotsukaharaphperkaigi2020

「明日からフロントもよろしく!」 と言われたとき備える Atom Design でのフロントエンド設計

秋葉 誠一 @akki_megane
https://speakerdeck.com/akki_megane/ming-ri-karahurontomoyorosiku-toyan-waretatokinibei-eru-atomic-design

Laravelで家電を操作してみよう

なずな @akaa07_pg
https://speakerdeck.com/akaa07/phperkaigi2020-laraveldejia-dian-wocao-zuo-sitemiyou

2020/02/11

自作して理解するxUnit

東口 和暉 @hgsgtk
https://speakerdeck.com/hgsgtk/self-made-xunit-final-edition

ジェネレータで無限を手玉に取る術

うさみけんた @tadsan

記事執筆時点では資料を見つけられず。

マスターデータの管理運用と実装について

武田 憲太郎 @KentarouTakeda
https://www.slideshare.net/KentarouTakeda/ss-227566415

実例から学ぶ、最後まで諦めない決済システム移行方法

中村光佑 @litencatt
https://speakerdeck.com/kosuke_nakamura/phperkaigi-2020

PHPerがこれから「型」とお付き合いしていくために

やなせ たかし @penguin_no_045
https://speakerdeck.com/penguin045/phpergakorekara-xing-toofu-kihe-isiteikutameni

PHPシステムをコンテナで動かすための取り組みのすべて

きくもと @takakiku
https://speakerdeck.com/kikumoto/phpsisutemuwokontenatedong-kasutamefalsequ-zu-mifalsesuhete

クリーンな実装を目指して

Hamee株式会社
https://speakerdeck.com/syarig/kurinnashi-zhuang-womu-zhi-site?slide=38

Zend VMにおける例外の実装

hnw @hnw
https://www.slideshare.net/hnw/zend-vm-227589843

PHPでPHPを実装する 〜プログラミング言語実装入門〜

ふりーだむ @tzm_freedom
https://speakerdeck.com/tzmfreedom/phpdephpwoshi-zhuang-suru-puroguraminguyan-yu-shi-zhuang-ru-men

もしもPHPソースコードが読めたなら...

富所 亮 @hanhan1978

記事執筆時点では資料を見つけられず。

ぼっちからはじめるレガシーカルチャー改善ガイド 〜はじめの一歩編〜

大橋 佑太 @blue_goheimochi

記事執筆時点では資料を見つけられず。

今だからこそ振り返る register_globals

gongo @gongoZ
https://speakerdeck.com/gongo/phperkaigi-2020

Webアクセシビリティを支えるための技術

有木 詩織 @shiori_pk
https://www.slideshare.net/shiorikoga/web-227766380

GitHub Actionsで始めるPHPアプリケーションのCI実践入門

高野福晃 @fortkle
https://speakerdeck.com/fortkle/ga-phperkaigi2020

Serverless Pattern

清家史郎 @seike460
https://slide.seike460.com/slides/phperkaigi2020#/

PHPでもVTuberになりたい!

ひかる @hico00
https://speakerdeck.com/hico00/phpdemovtuberninaritai-number-phperkaigi

PHPでleetCodeのeasyレベル100問ノック

yamotuki @yamotuki
https://speakerdeck.com/yamotuki/phpdeleetcodefalseeasyreberu100wen-falsetuku

PHP未経験者を育てる独自フレームワークの作り方

カンボ@沖縄 @kanbo0605
https://speakerdeck.com/bumptakayuki/phpwei-jing-yan-zhe-woyu-terudu-zi-huremuwakufalsezuo-rifang

RFCの歩き方

加納悠史 @YKanoh65
https://speakerdeck.com/ykanoh/how-to-read-php-rfc

PHPerKaigi2019への参加がきっかけで社内勉強会の主催するようになった話

MasaKu @MasaKu_e
https://speakerdeck.com/masaku_2019/phperkaigi2019hefalsecan-jia-gakitukakedeshe-nei-mian-qiang-hui-falsezhu-cui-suruyouninatutahua

PHPとRustを組み合わせて音声ファイルをエンコードする話

meteor @yaminoma_tw
https://speakerdeck.com/yaminoma/phptorustwozu-mihe-waseteyin-sheng-huairuwoenkodosuruhua

自分の名前を"ちゃんと"入力したい人生だった

hamaco @hamaco
https://speakerdeck.com/hamaco/zi-fen-falseming-qian-wo-tiyanto-ru-li-sitairen-sheng-datuta

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

php 配列 重複した要素の削除 array_unique()

配列の重複要素を削除

array_unique()

qiita.php
$array = ["aaaa","wwww","eeee","aaaa","tttt","WWWW","eeee"];
$array = (array_unique($array));
//Array ( [0] => aaaa [1] => wwww [2] => eeee [4] => tttt [5] => WWWW )
連想配列の場合
qiita.php
$array = array(
    array('takuya', 'jun', 'yoshiko', 'miki'),
    array('kazuki', 'aya', 'yoshiko', 'miki'),
    array('takuya', 'jun', 'yoshiko', 'miki'),
);

$array = array_unique($array, SORT_REGULAR);
//Array ( [0] => Array ( [0] => takuya [1] => jun [2] => yoshiko [3] => miki ) [1] => Array ( [0] => kazuki [1] => aya [2] => yoshiko [3] => miki ) )

第2引数にSORT_REGULARを指定しました。

配列内に要素が何種類あるか調べる。

qiita.php
$array = ["aaaa","wwww","eeee","aaaa","tttt","WWWW","eeee"];
$count = count(array_unique($array));
//5
qiita.php
$array = array(
    array('takuya', 'jun', 'yoshiko', 'miki'),
    array('kazuki', 'aya', 'yoshiko', 'miki'),
    array('takuya', 'jun', 'yoshiko', 'miki'),
);

$count = count(array_unique($array, SORT_REGULAR));
//2

//要素が全て同じ
$array = array(
    array('takuya', 'jun', 'yoshiko', 'miki'),
    array('takuya', 'jun', 'yoshiko', 'miki'),
    array('takuya', 'jun', 'yoshiko', 'miki'),
);

$count = count(array_unique($array, SORT_REGULAR));
//1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

file_get_contentsが失敗する

以下のサイトのサンプルを自前の環境で実行しようとしました。
https://note.com/kazztech/n/ndb3a5468f299

呼び出しプログラムを実行したところ、以下のエラーになりました。
PHP Warning: file_get_contents(http://192.168.33.10:8000/api.php?num=10): failed to open stream: HTTP request failed!

このURL自体(以下URL)は表示できました。
http://192.168.33.10:8000/api.php?num=10
結果として、{ "status": "yes", "x114": "1140", "x514": "5140" }と表示されました。

なぜエラーになるのでしょうか?

検索したところ、php.iniのallow_url_fopenの値の問題かもしれないとありましたが、これはOnでした。
WebサーバーはPHPのビルトインサーバーです。
PHPは7.1.33です。

補足1
絶対URLがうまく動いていないのかと思い、簡単なHTMLを試してみました。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Absolute URL</title>
</head>
<body>
<a href="http://192.168.33.10:8000/abso.html">test</a>
</body>
</html>

普通にうごきます。(abso.htmlに飛ばされました)

補足2
本丸のapi.phpにしてみました。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Absolute URL</title>
</head>
<body>
<a href="http://192.168.33.10:8000/api.php">test</a>
</body>
</html>

{ "status": "no" }と表示されました。(api.phpに飛ばされました)

絶対URLやapi.phpは問題ではなさそうです。

補足3
<?php
$data = file_get_contents("http://yahoo.co.jp/");
echo $data;

これだとうまくいく。(yahooが表示される)けど・・・
<?php
$data = file_get_contents("http://192.168.33.10:8000/api.php");
echo $data;

これだとダメ。
file_get_contentsは普通に動いているのだけど、PHPビルトインサーバーを指定したときはうまく動かないということなのかな・・・

補足4
こんな記事に行き当たった。
file_get_contents() で HTTP経由でデータを取得しようとしてエラーになったとき
https://qiita.com/matsuoshi/items/60a3240d0667272e0336
相手によってうまくいったり、いかなかったりしたというもの。
yahooはうまくいくけど、自前はうまくいかないというのはそっくり。
ただ、残念なことに?allow_url_fopenはONです。
しかし、相手によってうまくいったり、いかなかったりすることが、自身のphp.iniを更新することで変わるのが理解できない。
セキュリティ上、変なファイルを読み込んだらいけないとかなら、うまくいったり、いかなかったりするはずもなく、うまくいかないハズだ。

補足5
こんな記事に行き当たった。
allow_url_fopenが0のサーバーで外部ファイルを取得する方法
https://masshiro.blog/php-allow-url-fopen/
<?php
$url="http://192.168.33.10:8000/api.php";
$cp = curl_init();
/*オプション:リダイレクトされたらリダイレクト先のページを取得する*/
curl_setopt($cp, CURLOPT_RETURNTRANSFER, 1);
/*オプション:URLを指定する*/
curl_setopt($cp, CURLOPT_URL, $url);
/*オプション:タイムアウト時間を指定する*/
curl_setopt($cp, CURLOPT_TIMEOUT, 30);
/*オプション:ユーザーエージェントを指定する*/
curl_setopt($cp, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
$data = curl_exec($cp);
curl_close($cp);
echo $data;

コンソールにエラーは表示されないけど、api.phpからの返答は表示されない。
30秒待って呼び出し元とapi.phpの読み込みログが表示されるだけでした。
var_dumpしたら、bool(false)と表示された。
サーバーが原因と絞り込んでもよさそう・・・。

補足6
こんな記事に行き当たった。
PHPでURLが実際に存在するか確認する で、いろいろ勉強したのでメモ
http://blog.doli.jp/blog/2012/post473/
image.png
LocalもONでした・・・。

補足7
この記事をもとに試してみました。
https://www.sejuku.net/blog/24113
<?php
//コンテキストを設定
\$con = stream_context_create(array('http' => array('ignore_errors' => true)));
//URLを指定してレスポンスを取得する
$http = file_get_contents('http://192.168.33.10:8000/api.php', false, \$con);
//ステータスコードを取得する
\$code = substr($http_response_header[0], 9, 3);

var_dump($code);
bool(false)と表示されました。
原因が絞り込めないから絞り込めるようにしましょう。という記事なのに、残念です。

補足8
思考がハマってるっぽいので、根本から変えてみました。
絶対パスをやめて、相対パスで呼び出し。かつ、返却を簡素に。
〇呼び出し元
<?php
\$data = file_get_contents(dirname(__FILE__) . "/api.php");
var_dump($data);
〇api.php
<?php
print "response";
結果は、string(26) "と表示されました。ソース表示で確認すると、
string(26) "<?php
print "response";
"
こう表示されてました。
ソースがそのまま読み込まれてました。こんな仕様だとセキュリティ的におかしいと思うので、相対パスだから、こうなったのだと思います。
思考が切り替わらないという結果になりました。

補足9
そもそもの参考サイトに立ち返ってみました。
大本のサイトはapi.phpをlocalhost/api.phpとして呼び出しています。
私は192.168.33.10で呼び出しました。
VirtualBoxの呼び出し元を呼ぶときはこのアドレスで良いけど、file_get_contentsを使用するときは、このアドレスだとダメだという気がします。
かと言って、Localhostで呼べるように設定するのは大変そうです。
相対パスでは読み込めているので、相対パスありきで考えてみます。

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

VSCodeのデバッグ中にDeepExitExceptionが発生したら

VSCodeでphp開発始めたところ、デバッグ中にDeepExitExceptionが乱発する自体に遭遇したので、原因と対策。

結論

原因

デバッグ中にコード修正すると、phpcsが実行され、その中でDeepExitExceptionを発生させています。仕様に近い。何か問題が起きている訳ではないです。
具体的には、内部的に実行されているphpcsのコマンド引数をパースするところ(Config.php内)で、例外スローされています。(この例外スローが適切なのかは疑問)

対策

VSCodeのブレークポイントで、Everythingのチェックを外すだけです。標準でEverythingチェック入っています。

開発環境

VSCode
phpcs & phpcbf
xDebug

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

php繰り返し文

はじめに

私は今までRuby,railsを勉強していましたが、転職活動の末phpを使用する企業へ入社しそうなので、新しくphpを勉強する事とした。

初歩的な事から勉強しているので、公開して人の役に立つというより自分の理解度を確認するための記事(自己満)となりますので、間違っている可能性が十分あるので気になった方はご教示いただけたら幸いです。


早速本題に入ります。
phpの繰り返し文について、やり方は2種類ある。
1つはwhile文。もう1つはfor文。順にコードを書いていきます。

やりたい事:1〜365の数字を繰り返し表示させる

while文

 

<?php
  初期化処理
  while(繰り返す条件){
    繰り返したい処理
    更新処理
}
?>

これが基本の考え方で、実際コードを書くと、、、

<?php
$i = 1
while( $i <= 365) {
  print($i. "\n");
  $i++;
}
?>

となる。

補足として

\n = 改行

$i++
 ↓
$i += 1
と同じ意味

for文

<?php
for (初期化処理; 繰り返す条件; 更新処理 ) {
  繰り返したい処理
}
?>

これが基本の考え方で、実際コードを書くと、、、

<?php
for ($i=1; $i <= 365; $i++){
  print($i. "\n");
}
?>

となる。

簡単な処理の場合for文の方が少ない記述ですむ。

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

CORS回避と、ReactからAPIへのPOSTリクエストが正常に処理されなかった話

目的

ReactからaxiosでPOSTのAPIを叩きたい。

環境

React:ローカルの開発環境 http://localhost:3000/、
API:PHPで書かれており、開発サーバにおいてある https://api.example.com/***
同じドメイン上に無い環境です。

状況

ReactからaxiosでAPIにPOSTリクエストを投げているが、CORSエラーで弾かれます。

また、axiosからPOSTリクエストが正常に投げられていない、もしくはPHPが正常にPOSTリクエストを受け取れていないような挙動をします。

こんな状況です。

私は基本的にPHPの開発がメインのため、Reactは初心者です。
もちろんaxiosも初めて使います。

javascriptでAjax処理は書いたことがあるものの、コピペで動けば良い程度の使い方しかしませんでした。

また、とりあえずの解決は出来たものの、各機能の仕様を理解した上での対策ではないため記事に誤りがある可能性しかありません。
ご理解ください。

エラー1 CORSエラー

Access to XMLHttpRequest at 'https://api.example.com/****/****' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

対処

ググった結果
・fetchを使った{ mode : cors }で回避できるらしい
・axiosの設定を変えると回避できるらしい
・API側のheaderを弄れば回避できるらしい
・Chromeの拡張機能でCORS回避できるらしい
という記事を多数見つけ、拡張機能をなるべく使いたくなかったため、とりあえずReact側のコードをいじってみることに。
が、この対処法が悪手でした。

この時はまだ、「CORSってなんぞや???」という状況です。

react
import axios from 'axios'

axios.defaults.baseURL = 'http://localhost:3000'
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'
axios.defaults.headers.post['Access-Control-Allow-Origin'] = 'https://api.example.com'

エラーが変わら無いため、API側に下記コードを追加します。

php
  header("Access-Control-Allow-Origin: *");
  header("Access-Control-Allow-Headers: X-Requested-With, Origin, X-Csrftoken, Content-Type, Accept");
  header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH, HEAD");

エラー2 別のCORSエラー

その後も コード変更 → エラー → コード変更 → エラーを何回か繰り返しました。

Access to XMLHttpRequest at 'https://api.example.com/****/****' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.

対処

結局あきらめて以下の拡張機能を導入することに・・・。

allow-cors-access-control

ちなみにこの機能を有効化した状態でTwitterを開くと「Twitterおかしい?」というツイートができなくなるので、拡張機能の有効範囲を[ http://localhost:3000/ ]だけに限定する等の対策が必要です。
image.png

エラー3 明らかにおかしい分岐に入る

拡張機能入れたしこれで大丈夫!!
と思いつつ試してみたのですが、いまだエラー解消ならず・・・

Access to XMLHttpRequest at 'https://api.example.com/****/****' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

ただ、エラー文に気になることが書かれています。

It does not have HTTP ok status.

APIがok status(200)を返していないということは、PHP側で弾いている・・・?

原因の切り分け

ステータスコードを調べるためにデベロッパーツールで見てみることに。

image.png

ただし、failedとしか書いておらず、詳細を見ても不明とのこと。

仕方ないのでAPI側で例外処理をスキップすることに。

php
  try{
    if(!is_post_request()){
      throw new Exception("ERROR_INVALID_METHOD");
    }
...
...
  }catch(Exception $e){
    switch($e->getMessage()){
      case "ERROR_INVALID_METHOD":
...
        // コメントアウト!!
    //    set_header( HEADER_TYPE_400 );
      break;
      case "ERROR_INVALID_PARAMETER":
...
        // コメントアウト!!
    //     set_header( HEADER_TYPE_400 );
      break;
...
      default:
        set_header( HEADER_TYPE_500 );
    }
  }

例外を投げている箇所を特定してみると・・・

php
  if(!is_post_request()){
    throw new Exception("ERROR_INVALID_METHOD");
  }

どうやらPOST以外のmethodでリクエストを受け取っており、そのせいで400 Bad Requestを返しているらしい。

・・・・!?!?!?
axiosはPOSTを投げていないのか!?!?

それともPHPが正常にPOSTを受け取れていないのか!?!?

ちなみにis_post_request()はこんな感じ。

php
  function is_post_request(): bool {
    if( $_SERVER['REQUEST_METHOD'] === "POST" ) {
      return true;
    }
    return false;
  }

対処

さて困ったどうしたものか・・・
「axios初めて使うしなあ・・・しっかりPOST送れてるかな~・・・」
「まさかPHPがPOSTの判別を出来ないわけないよな~・・・」
「まあPHP側は大丈夫だと思うけどな~・・・」
「axiosとかcorsとかわかんねーよ・・・」
などと考えつつとりあえず原因がPHPに無いことを確定させたかったため、printデバッグしてみることに。

php
  if(!is_post_request()){
// 2行追加
      var_dump($_SERVER);
      exit;
    throw new Exception("ERROR_INVALID_METHOD");
  }

とりあえずこれで$_SERVER['method']に"POST"が入ってることが確認できるだろうと思いながらReactのページを見てみると・・・
image.png

おお!正常にjsonが返ってきた!
良かったよかった、これで先に進める・・・ え????????

「いやいやexitで強制終了してるんだからjsonが帰ってくるのはおかしいでしょ」
「というか$_SERVERの中身が表示されてないんですけど・・・?」
「どうなってんのこれ!?!?」
大パニックです。。。
ということでもう一箇所にprintデバッグの追加

php
    var_dump($_SERVER);
    exit;
    if(!is_post_request()){
      var_dump($_SERVER);
      exit;
      throw new Exception("ERROR_JSON_INVALID_METHOD");
    }

ようやく$_SERVERの情報が取得できました。

  ["REQUEST_METHOD"]=>
  string(4) "POST"

ただ、正常にPOSTで受け取っているようです。

「もしやis_post_request()関数が正常に機能していない・・・?」
ということになり、

php
// もとに戻した
    if(!is_post_request()){
      var_dump($_SERVER);
      exit;
      throw new Exception("ERROR_JSON_INVALID_METHOD");
    }
php
  function is_post_request(): bool {
// printデバッグ
    die($_SERVER['REQUEST_METHOD']);
    if( $_SERVER['REQUEST_METHOD'] === "POST" ) {
      return true;
    }
    return false;
  }

そして出力された文字列は

"POST"

ほう・・・
「"POST" === "POST"がfalseになるん・・・??」
「POSTの判別方法間違えてる・・・?」
「そもそも文字列の比較ってこれでできるっけ・・・?」
「"POST" == "POST"のほうが良いのかな・・・」
「いや変わんないわ・・・」
「PHPのコンパイル時にdie, exitの挙動が不安定になる・・・?」
「そもそもPHPでfunctionって使っていいの・・・?」
再び大パニック

printデバッグの出力先をエラーログに

なにか良いデバッグ方法は・・・と考えているときに
「これ本当にPOST投げてる?」
「というかPOST以外のリクエストも同時に投げてる?」
という疑問が湧き上がり、printデバッグの出力先を変えてみることに。

php
  error_log($_SERVER['REQUEST_METHOD'],0);
  if(!is_post_request()){
    var_dump($_SERVER);
    exit;
    throw new Exception("ERROR_INVALID_METHOD");
  }
error_log
[Tue Feb 11 17:34:14.278789 2020] [:error] [pid 15147] [client **.**.**.**:38309] OPTIONS, referer: http://localhost:3000/
[Tue Feb 11 17:34:14.664012 2020] [:error] [pid 15147] [client **.**.**.**:38309] POST, referer: http://localhost:3000/

案の定1回のリクエストでOPTIONS、POSTの順番にリクエストが投げられてました。

ということで原因はエラー1の対処で追加したコードの部分でした。

詳しくは省きますが、preflight requestの仕組みが悪さをしていたようです。

貴重な休日を真夜中(2:30頃)まで潰したCORSを私は許さない。。。

あとがき

その後もCORS関連のコードいじってたら結局拡張機能も要らなくなったため消しちゃいました。

デバッグの手法を教えるのって難しいですよね。
echoやdieでのprintデバッグを知っていたとしても、error_logでOSのシスログにエラーを投げる方法は知らなかったりとか。
かといって便利だからとあれもこれも教えても(教えられても)覚えきれないから意味がないみたいな。
こればっかりは場数ですかね。

あと、Twitterを開けない時ってめちゃくちゃ辛くないですか?
「Twitter開けない」ってつぶやきたいのにつぶやけないこの気持ち。
それはまるで好きな子に正直に好きと言えない初恋のような甘酸さ。

最後まで見ていただきありがとうございました。
今恋してるよーって人はいいねお願いします。

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

phpunit8に上げたらPHPUnit does not appear to be installed.と言われた

プロジェクトでphp7.3、fuelphp1.8.2を使っています。
しかし、phpunitのバージョンが5.6と古く、もうすぐサポートが切れてしまうためバージョンアップした際に出たエラーの解決について書いていきます。

前提

バージョンアップが完了しているわけではありません。
怒られたので、それの解決方法だけです。
もしかしたら、別の方法があるかもしれないので、その場合は是非コメントお願いします。

やったこと

本当はdocker内でコマンド叩いていますが、長くなってしまうので必要なところだけ抜粋します。
phpunitのバージョンアップに伴って依存パッケージの更新も必要だったため更新しています。

./composer.phar remove phpunit/dbunit
./composer.phar remove phpunit/phpunit
./composer.phar remove phpunit/php-timer
./composer.phar require --dev phpunit/phpunit ^8.5.2
./composer.phar require --dev phpunit/php-timer ^2.1.2
./composer.phar require --dev kornrunner/dbunit

上のコマンドを入れ終わった後、testを実行すると、エラーが発生する。
5.6の時はこれで実行できていました。

$ php oil test
Uncaught exception Oil\Exception: 0 - PHPUnit does not appear to be installed.

    Please visit https://phpunit.de and install. in /省略/fuel/packages/oil/classes/command.php on line 189
Callstack:
#0 省略/oil(68): Oil\Command::init(Array)
#1 {main}

解決方法

公式サイトの方にバージョンの確認方法載っていたので、それで確認。
バージョンアップは問題なくされている。
https://phpunit.de/getting-started/phpunit-8.html

$ ./vendor/bin/phpunit --version
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.

今までとパスが変わった?みたいです。
改めて、公式に沿って実行します。

$ ./fuel/vendor/bin/phpunit --bootstrap fuel/vendor/autoload.php ./fuel/app/tests
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.



Time: 150 ms, Memory: 4.00 MB

No tests executed!

一旦、タイトルのエラーが出なくなりました。
ただ、テストが一つも実行されていません。

ファイルを指定せずにテストを実行する場合には、ファイル名を *Test.phpとしないといけないようです。。
これ全ファイルに適用するの結構たいへん。。
1ファイルだけ変更したところ、正しく実行されました。(エラーが出ている状態)

Using tests instead of tests/EmailTest would instruct the PHPUnit command-line test runner to execute all tests found declared in *Test.php sourcecode files in the tests directory.

まとめ

バージョンアップを始めた直後にphpunit9のリリースがされたため、これからアップデートをする方はそちらへのアップデートを検討されると良いかと思います。

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