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

PHPとは(入門します)

PHP(ピー・エイチ・ピー ハイパーテキスト プリプロセッサー)を勉強しようと思います。

なぜ、この言語なのか。なぜやろうと思ったか。興味を持ったか。
まずそれを自分の中でアウトプットする意味で、この記事に書いて、勉強を進めて行こうと思います。

スクールでHTMLとCSS(Haml、SCSS)、JavaScript,Ruby,Railsなどを学びました。それらを利用して、簡単なアプリ開発を行いました。結果、僕が一番興味を持ったのは、フロントエンド側の実装工程です。

PHPでは、HTMLやJavaScriptなどでは叶えることない動きを叶えることができます。

まずは、環境構築をしてから、progateなどを用いていく予定です。

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

【Laravel】MySQL設定メモ

MySQLにデータベースを作成する

ターミナル
$ mysql -u root  

mysql > CREATE DATABASE アプリケーション名;

laravelファイルの.envを修正する 

.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=[アプリケーション名]
DB_USERNAME=root
DB_PASSWORD=
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CORS問題の対応の仕方をミスってた話

フロントエンドとサーバサイドプログラムの連携をRestっぽい感じでやるようになって久しいが
ここに来て結構なポカミスをやったので備忘録として。

フロントには固定値のjsonを返すだけのダミーAPIを作って実装を進めてもらい
バックエンドはprod用としてそのダミーが動的なものになるように実装、最終的にダミーと差し替え
みたいな流れで制作をしている。

で、バックエンドはphpを使うが色々ごちゃごちゃやった後に

test.php
色々やる
header('Content-Type: application/json');
echo $json;
exit;

としてjsonを出力するのがセオリーである。
フロントはtest.phpをエンドポイントとしてjsとかで実行するわけだが
大体いつも問題になるのがCORSである。

セキュリティリスクを追々考えなければと思いつつApache側で

Header set Access-Control-Allow-Origin *

レスポンスヘッダに全てのドメインからアクセスOK!!を付けている。
で、それをすっかり忘れてphpの方にも記載したのがまずかった。

header('Content-Type: application/json');
header('Access-Control-Allow-Origin *');
echo $json;
exit;
Access to XMLHttpRequest at 'https://xxxxx.jp/api/test' from origin 
'null' has been blocked by CORS policy: 
The 'Access-Control-Allow-Origin' header contains multiple values '*, *', 
but only one is allowed.

エラーメッセージの冒頭だけ見てOriginの設定してるはずなのにな〜って思ってたが
よく見るとAccess-Control-Allow-Originヘッダが複数設定されてるよってエラーだった。

どっちか消して解消、というかまぁこの場合はphpの方で良いと思うが。
分かってるんだけど気付かないパターンのミスが一番怖い。

おわり。

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

FuelPHPにおいてtimestampで範囲を指定して取得するとき注意点

概要

created_atupdated_at などに使われる timestamp 型は非常に便利で、不等号で簡単に比較ができるのですが、気を付けないとうまく動いてくれません。すごく基本的なことですが、場合によっては適当に書いても正常に動いてしまうことがあるので意外と気づきにくい部分だと思います。

本題

デフォルトの unixtime で扱っている場合
e.g.)1596447140 など
はそのまま使えて、

unixtime.php
$query = \DB::select()
->from('table_name')
->where('created_at', '>', strtotime('-1 week'))
->execute();

のように書くだけで比較ができます。この場合は1週間前よりあとに作られたレコードだけ取得します。

一方、違うフォーマットで扱っている場合
e.g.)2020-08-03 18:32:20 など
は注意が必要で、フォーマットも合わせてあげる必要があります。例の場合は、

anyformat.php
$query = \DB::select()
->from('table_name')
->where('created_at', '>', date('Y-m-d h:i:s', strtotime('-1 week')))
->execute();

と書くと同様の動きをしてくれます。

まとめ

しっかり使えば非常に便利なtimestamp。一つのサービスで使うtimestampのフォーマットは合わせた方が良いですね。
余談ですが、'Y-m-d h:i:s'と書くべきところを'Y-m-s h:i:s'と書いていて、更新するたびに取得されるレコードが違うということになったりもしました。さすがに気づきましたけど(30分ほど苦戦して上司に聞いて)。

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

Laravelでデータの作成、削除 save() create() delete()

save:テーブルを保存

$user = new App\User //モデルUserクラスのオブジェクト$userを作成
$user->name = "toto" //$user->nameプロパティに "toto"を代入
$user->save() //saveでDB内Userテーブルに新規保存できる。

create:テーブル作成

マスアサイメント対策(次項目でで説明)
をmodelに記入する
app/User.php

protected $fillable = ["name","body"];

と記入し、リクエストから変更できるカラムをname,bodyのみに制限する。

//DB内にUserを直接作成
App\User::create(["name"=>"piyo", "address"=>"tokyo"]);

マスアサイメントを忘れていると、MassAssignmentExceptionエラーが起こる。

マスアサイメントとは

説明:
ユーザーからリクエストされたデータに応じて、サーバ側はDB内のデータを変更する。
しかし、悪意のあるユーザや、バグにより、想定外のリクエストが行われ、
DB内の操作されたくないデータが操作されてしまう問題。

具体例:
DB内に、name address admin_modeのデータがあるとしたとき
入力フォームのnameの部分を、検証ツール等で書き換えを行い、
nameではなく、admin_modeで送る。
すると、admin_modeの変更リクエストをユーザが送ることができてしまう。
結果、一般ユーザーが管理者権限を持つことができてしまう。

補足:railsだとstrong parameterがマスアサイメント対策に該当する.

delete:テーブル削除

$user = App\User::find(1); //Userテーブル内id=1のデータを$userとして指定
$user->delete(); //$userに該当するUserテーブルデータを削除
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

なぜ、ログイン実装後に Register で 「Oops VFS connection does not exist」と表示されるのか

なぜ、ログイン実装後に Register で 「Oops VFS connection does not exist」と表示されるのか

このエラーの背景としては、
私は、Laravel で Auth 機能を用いてログイン機能を実装するところでした。
問題なく 「web.php 」と「Routing」と「Controller」はしっかり書けていました。

しかし、
https://gyazo.com/bdf3f4c82bc15c334adb050a5723b634
このように表示される模様。

エラー内容は

「Oops VFS connection does not exist」

ふむ。
直訳すると

「おっとっと VFS接続が存在しません」

おっとっとじゃないわw 泣きたいわw
多分、接続サーバー先の問題とかそう言った類の問題な気がすると思いました。
なぜなら、

その証拠に、ログインしないで実際に他の階層に降りようとすると、
Auth の設定で Login の画面に強制転送さられるからです。

この事件に陥る人が他にもいるのではと思い、
ネットで探していたところ、似たような方はいました。

その対処法としては、AWS の問題であるから、インスタンスを停止して、再度起動である。
しかし、何度やってもダメでした。
仕方なく私のメンターさんに相談したところ、解決策がありました。

解決策

それは Laravelの中にある TrustProxies.php の設定を変更することでした。
PATH:/プロジェクト名/app/Http/Middleware/TrustProxies.php

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array|string
     */
    protected $proxies;

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers = Request::HEADER_X_FORWARDED_ALL;
}


TrustProxies.phpのコードはこのようになっております。
変更点は以下の通りです。

/**
     * The trusted proxies for this application.
     *
     * @var array|string
     */
    protected $proxies = "**";

ワイルドカードの "" を $proxies に設定してあげることでした。
メンターさんになぜこうなるか聞いてみました。
” を付けてあげないと Http で通信している状態になってしまうからだそうです。
つまり "**" を付けてあげることで Https を許可していることになるそうです。

これで表示されるか
やってみました!!

表示されました!!
https://gyazo.com/104db9302114aa52ea579752a3afc41e

メンターさんすごい。。

最後に変更後のコードを記載しておきます。

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array|string
     */
    protected $proxies = "**";

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

これは忘れずに覚えておきます。
今日も積み上げたぜ٩( ᐛ )و

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

エラー「ERROR: The Compose file './docker-compose.yml' is invalid because:」の対処法

ERROR: The Compose file './docker-compose.yml' is invalid because:
services.php.volumes contains an invalid type, it should be an array

docker-compose upした時に上記のようなエラーが出た場合の対処法です。

原因はYaml文法エラー

エラー内容のservices.php.volumesの部分の文法が不正ということです。

Before : 不正な文法

(不正文法)docker-compose.yml
version: '3'
services:
  nginx:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./wwww/html:/var/www/html
    depends_on:
      - php
  php:
    build: ./php
    volumes:
      -./www/html:/var/www/html

After : 正しい文法

(正しい文法)docker-compose.yml
version: '3'
services:
  nginx:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./wwww/html:/var/www/html
    depends_on:
      - php
  php:
    build: ./php
    volumes:
      - ./www/html:/var/www/html

具体的には以下の部分です。

Before : 不正な文法

-./www/html:/var/www/html

After : 正しい文法

- ./www/html:/var/www/html

Yamlの文法は-(ハイフン)の後ろには必ず半角スペースが必要です。
うっかり見落としていました。

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

PHPフレームワークLaravelの使い方

環境構築

LAMP+Redis環境を超短手数で構築する
Composerをインストールする
eclipseでPHPの開発環境を構築する。デバッグでブレークポイントを付けてステップ実行できるようにする

実装

eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)
Laravelのテンプレートエンジンを使う
Laravelのレイアウトエンジンを使う
Laravelでビュー描画前処理を行う
Laravelでリクエストデータを取得する
Laravelでエラーハンドリング(エラー時の制御)を行う
Laravelでセッションを使う
Laravelでファイルアップロード機能を実装する
Laravelでファイルダウンロード機能を実装する
LaravelでJSONを返す
LaravelでJSONリクエストを受け取る
Laravelで前処理、後処理を実装する
Laravelで入力値エラーチェック(validate)を実装する
Laravelでリダイレクトを使う
Laravelでフラッシュデータ(直後のHTTPリクエストの間だけセッションに保存されるデータ)を使う
Laravelで多言語処理を行う
Laravelでログ出力を行う
Laravelで環境(開発環境と本番環境等)ごとに異なる値を定義する
Laravelでメール送信する
Laravelでコマンドライン処理を行う
Laravelのタスクスケジューラを使う
LaravelでDIを使う
LaravelでHTTPリクエストの単体テストを行う
LaravelでJSONリクエスト、JSONレスポンスの単体テストを行う
Laravelでコマンドライン処理の単体テストを行う
Laravelでデータベースを扱う準備をする
Laravelでテーブル作成
Laravelで初期データ投入
Laravelでデータベースを操作する(クエリビルダ編)
Laravelでデータベースを操作する(Eloquent編)
Laravelでページネーション
Laravelでキューを使う
LaravelでJavaScript、CSSを使う
Laravelの認証(web画面)
Laravelで認可(Gate)
Laravelで認可(Policy)
Laravelの認証(JWT)
LaravelでRedisを操作する
LaravelでWebSocket

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

【PHP8.0】非厳密な比較演算子`==`の挙動が今さら変更になる

推移律?
そんなものはこの世の果てに置いてきた。

PHP7
    "true" == 0;
    0 == "0";
    "true" == "0";

結果は順にtrue、true、falseです。

これがPHP7までの非厳密な比較(等価)演算子だったわけですが、まあおかしいよねってことで、この挙動がPHP8.0で変更になることになりました。
よもや今さら基本中の基本である比較演算子の動作を弄ってくるとは思わなかったぞ。

以下はSaner string to number comparisonsの日本語訳です。

PHP RFC: Saner string to number comparisons

Introduction

==やその他の非厳密な比較演算子を用いた文字列と数値の比較は、現在は、文字列を数値にキャストし、その後整数か浮動小数の比較を行っています。
この結果、多数の不可解な結果が得られますが、中でも注目すべきは0 == "foobar"がtrueになることです。
このRFCでは、文字列が実際に数値型文字列である場合にのみ数値型で比較を行うことにすることで、非厳密な比較をより直感的にし、問題になる動作が起こりにくくすることを提案します。
そうでない場合は、数値を文字列型に変換し、文字列比較を行うことにします。

PHPでは大別して2種類の比較演算子をサポートしています。
厳密な比較===および!==と、非厳密な比較==!=>>=<<=<=>です。
両者の主な違いは、厳密な比較は両方のオペランドが同じ型であることが前提で、暗黙の型変換を行わないことです。
しかし、それ以外にもいくつか異なる点が存在します。

・厳密な比較は、strcmp()で比較を行うが、非厳密な比較は、数値型文字列である場合は数値に変換する"スマートな"比較を行う。
・厳密な比較は、配列の順番も同じ必要があるが、非厳密な比較は、配列の順番が異なっていてもよい。
・厳密な比較は、オブジェクトが同一かをチェックするが、非厳密な比較は、オブジェクトの値を比較する。

非厳密な比較は常に避けるべきである、というのが現在のPHPでのドグマになっています。
バグの最大の原因は0 == "foobar"がtrueになるという事実でしょう。
これはin_array()switchなど、比較が暗黙のうちに行われている場合によく発生します。

$validValues = ["foo", "bar", "baz"];
$value = 0;
var_dump(in_array($value, $validValues)); // true ←

これは不幸なことです。
なぜならば、PHPのような言語では、非厳密な比較に価値がないということは決してないからです。
42"42"を同じ値であると見なすことは多くの場合において有用です。
さらにPHPでは言語側が暗黙のうちに変換を行うことがあります。
たとえば配列のキーが整数型文字列の場合は整数に変換されます。
さらに、switchのような幾つかの構文は非厳密な比較しかサポートしていません。

非厳密な比較の考え方には多くのメリットが存在しますが、しかし残念なことに現在の比較のセマンティクスは明らかに間違っており、非厳密比較全体の有用性を大きく落としてしまっています。

このRFCでは、文字列と数値の比較をより合理的にすることを意図しています。
数値文字列を数値と比較する際には、文字列を数値に変換してから比較し、これは現在と同じ動作です。
それ以外の場合は、数値を文字列に変換して文字列比較を行います。
このRFCによって、幾つかの単純比較がどのように変更されるかを次の表に示します。

Comparison Before After
0 == "0" true true
0 == "0.0" true true
0 == "foo" true false
0 == "" true false
42 == " 42" true true
42 == "42foo" true false

この仕組みを理解するためには、数値型文字列の比較を見てみるとよいでしょう。
上の表と、下の数値型文字列と文字列の比較の結果表を見比べてみましょう。
(下の表は、このRFCでは変更されません)

Comparison Result
"0" == "0" true
"0" == "0.0" true
"0" == "foo" false
"0" == "" false
"42" == " 42" true
"42" == "42foo" false

上記の説明は簡略化されたものです。
詳細な仕様は後述しますが、これだけで新しい仕様の直感的な動作と、どうしてこの仕様を選択したのかはわかると思います。

Proposal

このRFCは、以下の演算子と関数、およびこれ以外にも非厳密な比較を行う全ての操作に適用されます。

・演算子==!=>>=<<=<=>
・関数in_array()array_search()array_keys()strict=trueを渡さない場合
・ソート関数sort()rsort()asort()arsort()、`array_multisort()にSORT_REGULARを渡した場合

このRFCの正確な定義は以下の通りです。

$int <=> $string
 ・ $stringが正規の整数型文字列である場合、$int <=> (int)$string
 ・ $stringが正規の浮動小数型文字列である場合、(float)$int <=> (float)$string
 ・ それ以外の場合、strcmp((string)$int, $string)を1/0/-1に正規化した値

$string <=> $int
 ・ return -($int <=> $string)

$float <=> $string
 ・ $floatがNANであれば1
 ・ $stringが正規の整数型文字列である場合、$float <=> (float)$string
 ・ $stringが正規の浮動小数型文字列である場合、$float <=> (float)$string
 ・ それ以外の場合、strcmp((string)$int, $string)を1/0/-1に正規化した値

$string <=> $float
$floatがNANであれば1
return -($float <=> $string)

ここには幾つかの微妙な要素が要素が絡んでいます。

Well-formed numeric strings

正確な定義は言語仕様書に書かれていますが、正規の整数型文字列とは、オプションの空白の後に10進整数もしくは浮動小数リテラルが続くものであると簡潔に記載されています。
正規でない整数型文字列とは、正規の整数型文字列の末尾に追加の文字列があるものです。
それ以外の全ての文字列は、整数型文字列ではありません。

このRFCでは、正規の整数型文字列についての比較はこれまでと全く同じです。
これは42 == "42"のような単純なケースだけではなく、数値が異なる形式で与えられた場合でも同様です。

// 前後で変更なし
var_dump(42 == "000042");        // true
var_dump(42 == "42.0");          // true
var_dump(42.0 == "+42.0E0");     // true
var_dump(0 == "0e214987142012"); // true

文字列による非厳密な比較においても結果は全く同じです。

// 前後で変更なし
var_dump("42" == "000042");        // true
var_dump("42" == "42.0");          // true
var_dump("42.0" == "+42.0E0");     // true
var_dump("0" == "0e214987142012"); // true

このRFCによる差異は、正規でない数値文字列、もしくは数値型でない文字列の場合にのみ発生します。

                         // Before | After | Type
var_dump(42 == "   42"); // true   | true  | 正規
var_dump(42 == "42   "); // true   | false | 非正規
var_dump(42 == "42abc"); // true   | false | 非正規
var_dump(42 == "abc42"); // false  | false | 非数
var_dump( 0 == "abc42"); // true   | false | 非数

目を引くのは、" 42"と"42 "において結果が異なるところです。
この矛盾は、別途Saner numeric stringsのRFCにおいて解消されます。

Precision

比較方法を単純に定義するのではなく、数値を文字列にキャストして非厳密な比較を行うという回りくどい方法を採用しているのは、PHPによる浮動小数から文字列への変換はini設定precisionの影響を受けるからです。

正規の浮動小数文字列との比較は、この設定に無関係に処理されます。
しかし、非正規の浮動小数文字列との比較では、以下のように影響してきます。

$float = 1.75;

ini_set('precision', 14); // 小数点以下14桁、デフォルト
var_dump($float < "1.75abc");
// ↑↓だいたい同じ
var_dump("1.75" < "1.75abc"); // true

ini_set('precision', 0); // 小数点以下0桁
var_dump($float < "1.75abc");
// ↑↓だいたい同じ
var_dump("2" < "1.75abc"); // false

Special values

浮動小数には、幾つかの特殊な非数値が存在します。

                             // Before | After
var_dump(INF == "INF");      // false  | true
var_dump(-INF == "-INF");    // false  | true
var_dump(NAN == "NAN");      // false  | false
var_dump(INF == "1e1000");   // true   | true
var_dump(-INF == "-1e1000"); // true   | true

注目すべきが2点あります。
まず、INF"INF"と等しくなるようになりました。

ただし、NAN"NAN"と等しくなりません。
NANはあらゆる比較演算子にfalseを返し、<=>NANがどちらにあるかに関わらず1を返します。
これは、値が比較不可能であることを示すPHP内部の方法です。

NANの特別なセマンティクスはIEEE-754に従っており、NANを含む比較は常にfalseになります。

Backward Incompatible Changes

非厳密な比較のこの変更は、後方互換性がありません。
さらに悪いことに、このRFCはPHPコア機能を暗黙のうちに変更することになります。
PHP7.4ではある動作を行っていたコードが、PHP8.0では異なる動作を行うことになります。
そして影響を受けるケースを検出するために静的解析を使用すると、多くの誤検出が発生する可能性があります。

しかし比較の変更について調査を行ったところでは、この変更による実際の影響は、想像していたよりずっと小さいことがわかりました。
ただし、これはテスト対象のコードベースに大きく依存します。

投票

期間は2020/07/31まで、投票者の2/3の賛成で受理されます。
本RFCは賛成44反対1の圧倒的賛成多数で受理されました。

感想

変更後の動作を見るてみと、確かにこっちの方が元の動作よりも妥当っぽいよね、とは思うのですが、だからといって今さら==の挙動に手を加えてくるとはさすがに思いませんでしたよ。
なかなか思いきったことをする。

この変更によって自然と

switch('foo'){
    case 0:
        '$fooは0だよ';
}

switchでよく問題となっていたこの挙動が発生しなくなります。
ということはつまりmatch式いらないのでは?と一瞬思わないでもないですが、switchはそれ以外にも色々アレなのでやっぱりあったほうがいいですね。

さて、この変更により、きっと予想より多くのコードに影響が出ることでしょう。
RFC中では影響は意外と少ないと言っていましたが、それはあくまで少なくともGitHubに上げることのできる程度の能力がある者が書いたコードだからであり、最低限の水準は維持されているからです。

そう、GitHubに上げる能力すらない者たちによって書かれた絶望と混沌が世の中には溢れているのですよ。

と思ったけど、そんなコードはそもそもPHP8では動かないだろうし、そしてバックエンドで動いているだろうPHP5が8にアップグレードされるなんてこともないだろうから、別に問題ないか。
それに動作が変更になるといっても、基本的には想定したように動くようになる変更ですしね。
即ち、現実的なソースコードにはほとんど影響ありません。

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