- 投稿日:2020-01-03T23:54:13+09:00
PHPにおける配列のarray()と[]の違い
概要
PHP5.4以降で
array()の短縮構文として[]を使えるようになったがどちらがいいのか、またその理由や両者の違いを説明しますググり方
[]は日本語では角括弧、英語ではsquare bracketsといいます違い
可読性
可読性は正義です。他の言語(JSなど)では配列の宣言として
[]を使っています。なのでarray()ではなく[]の方が他の言語をメインとして使っている人も理解しやすいです。
また、array()だとこれは関数なのか、言語構成要素なのかなど実装に不要なことを考えることがありますが、[]だとそれがなく、コーディングの際の認知負荷も下がります。速度
速度が早くなるのかと期待していたのですがこちらの記事によると速度には違いがないそうです
https://blog.leko.jp/post/benchmark-with-syntax-and-language-structure/結論
[]を使いましょうPHP5.4以前なのだが???
PHP7.1ですらEOL(2020/1/3時点)なのでバージョン対応しましょう
謝辞
皆さんありがとうございました。
https://teratail.com/questions/230678#reply-336548
- 投稿日:2020-01-03T23:18:51+09:00
PHP7.4の性能比較 一億回のループとDB500万件からの検索
PHP7.4の性能比較を行ってみました。
現在契約しているレンタルサーバーのグレードをアップしようかなと思い、
ついでと言っては何ですが、どの程度性能が上がるかを確認するために、
PHPそれぞれのバージョンで性能を比較してみました。比較対象としたバージョンは以下の通りです。
■5.6.40
■7.0.33
■7.1.31
■7.2.21
■7.3.9
■7.4.1処理としてはPHPでは1億回の単純な足し算をfor文でぶん回す事と
DBはMarriaDBですが、インデックスが貼られていないテーブルから500万行より
適当な文字列の検索を行ってみました。実行結果としてはこのような感じになりました。
バージョン php処理時間 MySQL処理時間 5.6.40 10.96秒 3.21秒 7.0.33 04.90秒 3.06秒 7.1.31 04.56秒 3.06秒 7.2.21 03.17秒 2.72秒 7.3.9 02.50秒 3.15秒 7.4.1 02.10秒 3.11秒 この結果より導き出される事は以下三点です。
DBの処理はPHPのバージョンに依存しない
処理時間の差については誤差の範囲です。
5.X系はすぐにでも7.x系にかえましょう
雲泥の差です。
7.x系は大差なしです。
確かにバージョンが上がるたびに処理速度が上がっています。
しかし、この処理はこのような計算です。
$j=0;
for ( $i = 0; $i < 100000000; $i++ ) {
$j=$j+$i;
}
何かコードを描く際に1億回ものループ処理を行うことって通常は無いですよね?
あっても100回~1000回程度だと思います。
コンマ何秒の世界を生きているわけではないのであれば無理してバージョンアップをする必要はないですね。
- 投稿日:2020-01-03T23:16:01+09:00
PHP 画像のアップロード
画像をアップロードし表示させる
【大まかな流れ】
◆フォームから画像を選択
◆画像ファイルかのチェック
◆DBではなくサーバーのimageディレクトリに画像を保存
◆DBにはファイル名保存
◆DBのファイル名を元に画像表示ファイル構造
test |-images (画像ファイル保存ディレクトリ) |-image.php(画像表示ファイル) |-upload.php(画像アップロード処理)画像ファイル送信フォーム
upload.php<h1>画像アップロード</h1> <!--送信ボタンが押された場合--> <?php if (isset($_POST['upload'])): ?> <p><?php echo $message; ?></p> <?php else: ?> <form method="post" enctype="multipart/form-data"> <p>アップロード画像</p> <input type="file" name="image"> <button><input type="submit" name="upload" value="送信"></button> </form> <?php endif;?>・
送信ボタンが押された場合の処理を記載
・アップロードされたファイルが画像ファイルではなかった場合はエラーメッセージ
・inputタグでfileを指定upload.php<?php $dsn = "mysql:host=localhost; dbname=xxx; charset=utf8"; $username = "xxx"; $password = "xxx"; try { $dbh = new PDO($dsn, $username, $password); } catch (PDOException $e) { echo $e->getMessage(); } if (isset($_POST['upload'])) {//送信ボタンが押された場合 $image = uniqid(mt_rand(), true);//ファイル名をユニーク化 $image .= '.' . substr(strrchr($_FILES['image']['name'], '.'), 1);//アップロードされたファイルの拡張子を取得 $file = "images/$image"; $sql = "INSERT INTO images(name) VALUES (:name)"; $stmt = $dbh->prepare($sql); $stmt->bindValue(':image', $image, PDO::PARAM_STR); if (!empty($_FILES['image']['name'])) {//ファイルが選択されていれば$imageにファイル名を代入 move_uploaded_file($_FILES['image']['tmp_name'], './images/' . $image);//imagesディレクトリにファイル保存 if (exif_imagetype($file)) {//画像ファイルかのチェック $message = '画像をアップロードしました'; $stmt->execute(); } else { $message = '画像ファイルではありません'; } } } ?> <h1>画像アップロード</h1> <!--送信ボタンが押された場合--> <?php if (isset($_POST['upload'])): ?> <p><?php echo $message; ?></p> <p><a href="image.php">画像表示へ</a></p> <?php else: ?> <form method="post" enctype="multipart/form-data"> <p>アップロード画像</p> <input type="file" name="image"> <button><input type="submit" name="upload" value="送信"></button> </form> <?php endif;?>【保存している画像名について】
ユーザーが上げた画像名が使われてしまう形になっていると下記の問題が発生する。
・悪意のあるユーザーに、サーバーに不具合が発生するような名前を設定される恐れがある
・長い名前を設定されていると保存できない
・保存できないファイル名がある(?.jpgなど)
$image = uniqid(mt_rand(), true);
画像ファイル名をサーバー側でユニーク化して保存【画像アップロードについて】
画像ファイルかの確認を行わないと画像ファイル以外をアップロードした際も保存が完了され、空の画像が表示される
$image .= '.' . substr(strrchr($_FILES['image']['name'], '.'), 1);で拡張子を取得
exif_imagetype関数で画像ファイルかの確認
参考:exif_imagetype【画像の保存方法について】
データベースに画像を保存してしまうと、ユーザーデータを取得する時に画像データが大きい為、データ取得の時間が長くなってしまう恐れがある。
move_uploaded_file($_FILES['image']['tmp_name'], './images/' . $image);でディレクトリに画像を保存
そのため下記手順で表示させる
1. サーバー内に画像を保存する為のディレクトリを作成(images)
2. 1のディレクトリに画像を保存、画像のファイル名をデータベースに保存(ユニーク化されたもの)
3. データベースの画像のファイル名を元に画像を表示させる画像表示
image.php<?php $dsn = "mysql:host=localhost; dbname=xxx; charset=utf8"; $username = "xxx"; $password = "xxx"; $id = rand(1, 5); try { $dbh = new PDO($dsn, $username, $password); } catch (PDOException $e) { echo $e->getMessage(); } $sql = "SELECT * FROM images WHERE id = :id"; $stmt = $dbh->prepare($sql); $stmt->bindValue(':id', $id); $stmt->execute(); $image = $stmt->fetch(); ?> <h1>画像表示</h1> <img src="images/<?php echo $image['name']; ?>" width="300" height="300"> <a href="upload.php">画像アップロード</a>参考
アップロードファイルの情報を取得する:$_FILES
スーパーグローバル変数の$_FILESは5つの情報が格納されている
- 投稿日:2020-01-03T20:29:35+09:00
PHPで開発してて出会ったエラー達[随時更新]
- 投稿日:2020-01-03T19:25:40+09:00
PHPのデバッグ方法を分かりやすく解説
PHPのデバック方法を分かりやすく解説していきます。
「var_dump」で画面に出力する方法ではなく、エラー内容や処理の流れをログファイルに出力する方法になります。
ログファイルに出力する事で、実装した処理が正常に動作しているか確認できたり、不具合の原因を特定できたりします。準備
まずは下記のファイルを用意します。
・sample.php・・・・・・デバッグをしたいファイル
・functions.php・・・・・php.iniの設定やデバッグ関数を記述するファイルデバッグをしたいファイルの上部でfunctions.php を読み込んでおきます。
sample.php// functions.php を読み込み require('functions.php');ログの出力設定
functions.phpにログの設定を記述します。
ini_setとはPHPの設定をいじることができる関数です。出力するエラーレベルに関しては公式ドキュメントを参考にして下さい。
https://www.php.net/manual/ja/function.error-reporting.phpfunctions.php//================================ // ログ //================================ //エラー表示/非表示の設定 ini_set('log_errors','on'); //エラーレベルを設定 ini_set('error_reporting', E_ALL); //ログの出力ファイルを指定 ini_set('error_log','php.log');とりあえずこれだけでもエラーがあった場合にログファイルが自動生成され、原因をつきとめる事ができます。
例えばこんな感じです。
下記の場合だと「Undefined variable」なので、変数が定義されていないというエラーになります。php.log[03-Jan-2020 17:38:07 Asia/Tokyo] PHP Notice: Undefined variable: options in /Users/ray/Dropbox/05_mamp/htdocs/example/functions.php on line 195 [03-Jan-2020 17:38:07 Asia/Tokyo] PHP Notice: Undefined variable: mail in /Users/ryu/Dropbox/05_mamp/htdocs/example/functions.php on line 73よくあるエラーメッセージ
エラーメッセージ 説明 Fatal error: Call to undefined function 定義していない関数を呼び出しています。 Parse error: syntax error, unexpected end of file, expecting ‘,’ or ‘;’ あるべき文字が抜けています。 Fatal error: [] operator not supported for strings 文字型の変数を配列として扱おうとしています。 Warning: Missing argument 2 for (関数名) 関数に必要な引数が省略されています。 デバッグの方法
function.phpにデバッグ関数を記述。
functions.php//================================ // デバッグ //================================ //デバッグフラグ $debug_flg = true; //デバッグログ関数 function debug($str){ global $debug_flg; if(!empty($debug_flg)){ error_log('デバッグ:'.$str); } }例えばこのようなプログラムがあったとします。
sample.phpif(true){ //処理A debug("処理Aが成功しました"); //処理B debug("処理Bが成功しました"); }else{ //処理C debug("処理Cが成功しました"); //処理D debug("処理Dが成功しました"); } debug("処理終了");php.logにはこのように出力されプログラムの処理を追う事ができます。
php.log[03-Jan-2020 18:49:16 Asia/Tokyo] デバッグ:処理Aが成功しました [03-Jan-2020 18:49:16 Asia/Tokyo] デバッグ:処理Bが成功しました [03-Jan-2020 18:49:16 Asia/Tokyo] デバッグ:処理終了変数の値を分かりやすくログに表示する方法
print_r とは、指定した変数に関する情報を解りやすく出力する関数です。
第二引数にtrueを入れる事で戻り値として結果を表示させる事ができます。php.log$array = array( '野菜' => 'キャベツ', '果物' => 'りんご', '飲み物' => 'コーラ', ); debug(print_r($array,true));php.logではこのように分かりやすく表示されます。
DB接続時や、セッションの中身の確認する際に便利です。php.log[03-Jan-2020 19:03:09 Asia/Tokyo] デバッグ:Array ( [野菜] => キャベツ [果物] => りんご [飲み物] => コーラ )
以上PHPのデバッグ方法に関する解説でした。
記載内容に誤りがあったら、ご指摘いただけると助かります。
- 投稿日:2020-01-03T16:27:33+09:00
Laravelで簡単なメモアプリを作る(2)~データの取得と保存~
はじめに
Laravelとは、Webに特化したPHPフレームワーク。セキュリティ対策(CSRF対策など)もこれでできます。MVCアーキテクチャを採用しています。今回は、データベースの接続とデータの取得と保存をしていきたいと思います。
Laravelで簡単なメモアプリを作る(1)~Viewの作成と表示~
Laravelで簡単なメモアプリを作る(3)~データの更新と削除~今回の目標物
データ取得の流れ
データベースの作成
データベースの作成
phpMyAdminを開く。Newを押して、新しくdatabaseを作成する。今回は、memoAppという名前のデータベースを作成する。
データベースの接続
.envに、データベースの情報を記述する。DB_DATABESEには、データベースの名前を入れる。MAMPを使っている人は、デフォルトでユーザー名、パスワード共にrootなので特に設定していなければ、下の内容をそのままコピー&ペーストして良い。
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=memoApp DB_USERNAME=root DB_PASSWORD=rootモデルの作成
ターミナルまたは、PowerShellを開き、cdコマンドでmemoAppまで移動し、下のコマンドを打ち込む。
$ php artisan make:model Memo先ほど作成したモデルにDBのテーブル情報を記述する。
app/Memo.php<?php namespace App; use Illuminate\Database\Eloquent\Model; class Memo extends Model { // table名を指定 protected $table = 'memo'; // カラムを指定 protected $fillable = [ 'id', 'title', 'content' ]; // created_atを使わない場合はfalseを指定する。 public $timestamps = false; }テーブルの作成
マイグレーションファイルの作成
ターミナルまたはpowerShellを開いて、cdコマンドでmemoAppまで移動し、下のコマンドを打つ。
$ php artisan make:migration create_memo_table先ほど、生成したファイルにカラムの情報を記述する。
id : bigInt型
title : string型 24桁
content: string型 60桁database/migrations/2019_12_12_114638_create_memo_table.php<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateMemoTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('memo', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('title', 24); $table->string('content', 60); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('memo'); } }seederの作成
ターミナルまたはpowerShellを開いて、cdコマンドでmemoAppまで移動し、下のコマンドを打つ。
$ php artisan make:seeder MemoTableSeederdatabase/seeds/MemoTableSeeder.php<?php use Illuminate\Database\Seeder; use App\Memo; class MemoTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { DB::table('memo')->delete(); //最初に全件削除 Memo::create([ 'title' => '12/13 laravel勉強会', 'content' => 'データの読み取りと追加をやりました。' ]); Memo::create([ 'title' => '12/6 laravel勉強会', 'content' => 'Viewの表示をしました。' ]); } }database/seeds/DatabaseSeeder.php<?php use Illuminate\Database\Seeder; use Illuminate\Database\Eloquent\Model; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { Model::unguard(); $this->call('MemoTableSeeder'); Model::reguard(); } }マイグレーションとSeederの反映
phpMyAdminにマイグレーションとSeederを反映させる。ターミナルまたはpowerShellを開いて、cdコマンドでmemoAppまで移動し、下のコマンドを打つ。
$ php artisan migrate --seedphpMyAdminを開いて、反映されているかどうか確認する。
ちなみに、エクセル形式で表すとこんな感じになる。
データの取得
データをViewに渡す
app/Http/Controllers/MemoController.phpに、データをViewに渡すメソッドを記述する。
app/Http/Controllers/MemoController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Memo; class MemoController extends Controller { public function showHome() { $memos = Memo::get(); return view("home", ['memos' => $memos]); } public function showSubmit() { return view("submit"); } }とりあえずresources/views/home.blade.phpの適当なところに
<div>{{$memos}}</div>を入れて、表示を確認してみよう。$memosの中身が、id,title,contentの要素を持つオブジェクトの配列になっていることがわかる。
resources/views/home.blade.phpの適当なところに以下のコードを追加してみる。
<div>{{$memos[0]->id}}</div> <div>{{$memos[0]->title}}</div> <div>{{$memos[0]->content}}</div>ループを使わずにこんな感じで表示してみよう。
home.blade.php<tbody> <tr> <td class="left">{{$memos[0]->title}}</td> <td><a href="{{ route('submit')}}">編集</a></td> <td><a>削除</a></td> </tr> <tr> <td class="left">{{$memos[1]->title}}</td> <td><a href="{{ route('submit')}}">編集</a></td> <td><a>削除</a></td> </tr> </tbody>これをループを用いて、記述するとこんな感じになる。
resources/views/home.blade.php<tbody> @foreach ($memos as $memo) <tr> <td class="left">{{$memo->title}}</td> <td><a href="{{ route('submit')}}">編集</a></td> <td><a>削除</a></td> </tr> @endforeach </tbody>bladeファイルはhtmlファイルと違って、@を使うことで直接、条件分岐やループ文を記述することができる。中括弧で囲む必要がないが
@endifや@endforなどで閉じてやる必要がある。詳細画面にもデータを渡す
URLからメモのidを受け取れるようにする。URLを
‘/submit/{id?}’にすることで、idという変数をコントローラに渡すことができる。routes/web.php<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('sample'); }); Route::get('/home', 'MemoController@showHome')->name('home'); Route::get('/submit/{id?}', 'MemoController@showSubmit')->name('submit');リンクを押すと、URLにidを含んだページに飛べるようにする。
href="{{ route('submit', ['id' => $memo->id])}}"とすることで、URLにidを渡すことができる。resources/views/home.blade.php@extends('layouts.app') @section('css') <style> header { height: 50px; background-color: #000; color: white; padding-left: 20px; font-size: large; color: #ddd; } .title { position: absolute; top: 10px; } .card { margin-top: 40px; } .left { width: 70%; } .submit { position: absolute; top: 10px; right: 20px; } </style> @endsection @section('content') <div class="card" style="width: 100%;"> <div class="card-header"> メモ一覧 <a href="{{ route('submit')}}" class="submit">メモを追加</a> </div> <table class="table"> <tbody> @foreach ($memos as $memo) <tr> <td class="left">{{$memo->title}}</td> <td><a href="{{ route('submit', ['id' => $memo->id])}}">編集</a></td> <td><a>削除</a></td> </tr> @endforeach </tbody> </table> </div> @endsectionコントローラーの関数の引数に、URLの変数を渡せる。
app/Http/Controllers/MemoController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Memo; class MemoController extends Controller { public function showHome() { $memos = Memo::get(); return view("home", ['memos' => $memos]); } public function showSubmit($id = 0) { if ($id != 0) { $memo = Memo::where('id', $id)->get()->first(); } else { $memo = (object) ["id" => 0, "title" => "", "content" => ""]; } return view("submit", ['memo' => $memo]); } }コントローラから送られてきたデータを、Viewに埋め込む。
resources/views/submit.blade.php@extends('layouts.app') @section('css') <style> header { height: 50px; background-color: #000; padding-left: 20px; font-size: large; color: #ddd; } .title { position: absolute; top: 10px; } .container { margin-top: 40px; } </style> @endsection @section('content') <form> <div class="form-group"> <label for="title">タイトル</label> <input type="text" class="form-control" id="title" name="title" value="{{$memo->title}}"> </div> <div class="form-group"> <label for="content">内容</label> <input type="text" class="form-control" id="content" name="content" value="{{$memo->content}}"> </div> <a href="{{ route('home')}}" class="btn btn-primary">戻る</a> <button type="submit" class="btn btn-success">追加</button> </form> @endsectionデータの保存
postを使って、データをコントローラに送る。
resources/views/submit.blade.php@extends('layouts.app') @section('css') <style> header { height: 50px; background-color: #000; padding-left: 20px; font-size: large; color: #ddd; } .title { position: absolute; top: 10px; } .container { margin-top: 40px; } </style> @endsection @section('content') <form method="POST" action="{{ route('submit', ['id' => $memo->id])}}"> @csrf <div class="form-group"> <label for="title">タイトル</label> <input type="text" class="form-control" id="title" name="title" value="{{$memo->title}}"> </div> <div class="form-group"> <label for="content">内容</label> <input type="text" class="form-control" id="content" name="content" value="{{$memo->content}}"> </div> <a href="{{ route('home')}}" class="btn btn-primary">戻る</a> <button type="submit" class="btn btn-success">追加</button> </form> @endsectionMemoControllerに、Viewから送られてきたデータをデータベースに保存するメソッドを記述する。
app/Http/Controllers/MemoController.php<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Memo; class MemoController extends Controller { public function showHome() { $memos = Memo::get(); return view("home", ['memos' => $memos]); } public function showSubmit($id = 0) { if ($id != 0) { $memo = Memo::where('id', $id)->get()->first(); } else { $memo = (object) ["id" => 0, "title" => "", "content" => ""]; } return view("submit", ['memo' => $memo]); } public function postSubmit(Request $request, $id = 0) { $title = $request->input('title'); $content = $request->input('content'); if ($id == 0) { Memo::create([ 'title' => $title, 'content' => $content ]); } return redirect()->route('home'); } }web.phpに以下の内容を記述する。
routes/web.php<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('sample'); }); Route::get('/home', 'MemoController@showHome')->name('home'); Route::get('/submit/{id?}', 'MemoController@showSubmit')->name('submit'); Route::post('/submit/{id?}', 'MemoController@postSubmit')->name('submit');今回はここまで。次回は、データの編集と削除をしたいと思います。
- 投稿日:2020-01-03T15:58:37+09:00
【2020年1月】令和だし本格的にVSCodeのRemote Containerで、爆速の"開発コンテナ"始めよう
VSCode の Remote Conainer で"開発環境+プロジェクト全部入りのコンテナ"から開発をスタートダッシュをキメませんかッ!?
開発でVS Code の Remote Conainer使っていますか?単に既存のコンテナに入るだけなら Remote SSH でも構いませんが、"ローカル開発環境の一部"として、いやむしろローカルの開発環境=Remote Containerとして、ビンビンにRemote Container使っていきましょう。令和だし!
(すでに2年だけどね・・・?)特にMacを使っていると最初からPythonやらPHPやらRubyやらが入ってしまっているので開発環境があるのですが、これらは割とmacOSのエコシステムに組み込まれているので不要にパッケージの追加削除、できないのですよ。
brewとか意外とあっさり壊れますしね・・・。特にバージョンアップなんてもってのほかです。全然、余裕でおかしくなります。
そんなわけでMacに入っているPythonやRubyでプログラミングをバリバリしていると・・・ふと、後戻りできない状況になったりするわけです。
そんなことにならないためにも、"Remote Containerでの開発"に入門しましょう~!"Dev Container" 機能のご紹介
Remote Container の本家サイトと本家Githubで"Try a dev container"と言う項目とリポジトリがあるの、ご存知でしょうか?
"dev container"は開発環境入りコンテナが付属したプロジェクトのサンプルで、以下の各言語向けにdev-containerのサンプルが用意されています。
- Node.js, Javascript
- Python
- Go
- Java
- .Net Core
- PHP
- Rust
- C++
例えば、node.js、Javascript用のサンプルは以下のようなツリーになっております。
% git clone https://github.com/Microsoft/vscode-remote-try-node nodejs-dev-sample % cd nodejs-dev-sample % tree -a -I ".git" . ├── .devcontainer │ ├── Dockerfile │ └── devcontainer.json ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .vscode │ └── launch.json ├── LICENSE ├── README.md ├── package.json ├── server.js └── yarn.lock
treeコマンドで.gitだけ除外して全て表示すると上記のようになります。
ここで気になるのが・・・.devcontainerですよね?!
中身のDockerfileとdevcontainer.jsonは以下のようになっております。#------------------------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- FROM node:10 # The node image includes a non-root user with sudo access. Use the "remoteUser" # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs # will be updated to match your local UID/GID (when using the dockerFile property). # See https://aka.ms/vscode-remote/containers/non-root-user for details. ARG USERNAME=node ARG USER_UID=1000 ARG USER_GID=$USER_UID # Avoid warnings by switching to noninteractive ENV DEBIAN_FRONTEND=noninteractive # Configure apt and install packages RUN apt-get update \ && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ # # Verify git and needed tools are installed && apt-get -y install git iproute2 procps \ # # Remove outdated yarn from /opt and install via package # so it can be easily updated via apt-get upgrade yarn && rm -rf /opt/yarn-* \ && rm -f /usr/local/bin/yarn \ && rm -f /usr/local/bin/yarnpkg \ && apt-get install -y curl apt-transport-https lsb-release \ && curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \ && echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update \ && apt-get -y install --no-install-recommends yarn \ # # Install eslint globally && npm install -g eslint \ # # [Optional] Update a non-root user to UID/GID if needed. && if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \ groupmod --gid $USER_GID $USERNAME \ && usermod --uid $USER_UID --gid $USER_GID $USERNAME \ && chown -R $USER_UID:$USER_GID /home/$USERNAME; \ fi \ # [Optional] Add add sudo support for non-root user && apt-get install -y sudo \ && echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ && chmod 0440 /etc/sudoers.d/$USERNAME \ # # Clean up && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* # Switch back to dialog for any ad-hoc use of apt-get ENV DEBIAN_FRONTEND=dialog上記の解説は後に回して、続いて
devcontainer.jsonの中身は・・・devcontainer.json{ "name": "Node.js Sample", "dockerFile": "Dockerfile", // Use 'appPort' to create a container with published ports. If the port isn't working, be sure // your server accepts connections from all interfaces (0.0.0.0 or '*'), not just localhost. "appPort": [3000], // Comment out the next line to run as root instead. "remoteUser": "node", // Use 'settings' to set *default* container specific settings.json values on container create. // You can edit these settings after create using File > Preferences > Settings > Remote. "settings": { "terminal.integrated.shell.linux": "/bin/bash" }, // Specifies a command that should be run after the container has been created. "postCreateCommand": "yarn install", // Add the IDs of extensions you want installed when the container is created in the array below. "extensions": [ "dbaeumer.vscode-eslint" ] }と、コンテナの設定とvscodeのsettings.jsonの混ざったような形式のファイルとなっております。
この設定がなんなのかはだいたい、想像がつくかと思いますが、このフォルダをVSCodeで開くと何が起こるのでしょうか…試してみましょう!
その前に、Dockerデーモンが立ち上がってない方は事前にDockerデーモンを立ち上げてください。WindowsやMacの方はDocker for Desktopを起動しておいてください。Dockerが起動したらVSCodeを立ち上げて
nodejs-dev-sampleを開いてみます。
すると・・・
dev container configurationが見つかったからコンテナで開くか?と言う問い合わせが出ました!
そしてReopen in Containerをクリックすると・・・
しばらく時間が経って…開きました!左下のステータスバーがグリーンに変わってRemote接続中であることと、"Dev Container: Node.js Sample"の文字が眩しいですね!
そうなんです。.devcontainerフォルダとdevcontainer.jsonの設定と然るべきDockerfileが揃っていればプロジェクトフォルダ内のファイルを丸ごとマウントしたコンテナの自動生成とプロジェクトをVSCodeで開くのを勝手にやってくれるのです!また、ここでのDockerfileの特筆するべき点はdockerに問題の"root"ユーザー問題を宜しく解決してくれている点です。
"コンテナの中で開発する"のは聞こえはいいですが、大抵のイメージはそのまま実行するとユーザーが"root"になってしまうのが多いので、コンテナの中で更新されるファイルのオーナーが"root"になってしまってウザい問題がありました。自前でDockerfile書けばなんとでもなるのですがいちいち書いてられないし、いちいちDockerfile書くくらいなら開発環境汚れても別にいいじゃん会社のPCだし、みたいなことになってますよね!?
このDockerfileでは丁寧にそこのところをサポートしてくれているので、rootユーザー問題が無事に解決されています。ついでにコンテナの管理も VSCode からできるので…
このようにどのプロジェクトでどのコンテナ使っているかは、VSCodeから管理できます。
特にDockerではどのフォルダのDockerfileで立ち上げたコンテナかがわからない(と言うかイメージを作ってコンテナ起動するのでどこのフォルダのDockerfileで作成したイメージかどうかは本来は関係ないハズ)のでこれは便利です。どうやって使うのか?
まずは本家リポジトリのご紹介をしておきましょう。
リポジトリ名からどの言語向けのサンプルプロジェクトかわかると思います。
- microsoft/vscode-remote-try-python
- microsoft/vscode-remote-try-node
- microsoft/vscode-remote-try-go
- microsoft/vscode-remote-try-java
- microsoft/vscode-remote-try-cpp
- microsoft/vscode-remote-try-dotnetcore
- microsoft/vscode-remote-try-rust
- microsoft/vscode-remote-try-php
実際には
.devcontainerフォルダとdevcontainer.jsonの設定と使用されるDockerfileの3つが揃っていれば自動的にやってくれますので、これらのファイルのみを本家リポジトリからコピペで作成するのもアリです。
とは言え毎度コピペするのも面倒なので、以下のように手順を整えてしまいましょう。1. 雛形としてクローン
上記のリポジトリを以下のように"プロジェクトの名前"でクローンしてきます。
$ git clone https://github.com/microsoft/vscode-remote-try-php my-first-phpリポジトリ名の後ろの引数が作成されるフォルダ名で、別名でクローンするのが第1のミソです。
2. 履歴の削除と再作成
当然、cloneしたばかりでは本家リポジトリのコミット履歴を全て含んでおります。
このまま再利用しても全然、良いのですが・・・大抵の場合は気になるでしょう。
そこで.git以下をざっくり削除します。$ rm -rf .gitこれで過去のコミット履歴は綺麗さっぱり忘れました。
ついでに不要なファイルは削除しておきましょう。
なんなら.devcontainer以外は不要です。.vscodeはお好みにお任せいたします。で、お掃除が完了したところで新規のリポジトリとして初期化します。
$ git init ...これで新たな開発コンテナプロジェクトとして第一歩が始まりました!
3. Dockerfile のカスタマイズ
実際の案件ではDBやAngular、Aws、Firebase、AzureなどのCLIなどなどなどを入れる必要があったりするのでDockerfileがそのまま使えることはほぼないです。
よってDockerfileに予め必要なツールをインストールするコマンドを入れておきます。Dockerfileの記述に関してはここでは割愛させていただきます。。。
4. devcontainer.json のカスタマイズ
devcontainer.jsonではプロジェクト名や転送するポート、必要なVSCodeのプラグインなどの設定ができ、とても重要なファイルです。
設定値のリストは以下の箇所にあります。サンプルリポジトリの中で使われている設定をいくつか抜き出してみますと・・・
- settings … これはコンテナの中で使用される VSCodeのsetting.jsonの設定値となります。
- appPort … 転送するポートです
- postCreateCommand … コンテナ作成後に実行されるコマンド
- extensions … リモート側にインストールされるVSCodeプラグイン
あたりが重要なカスタマイズするポイントでしょうか。
また、リファレンスによると
docker-compose.ymlも利用可能な模様です。これらを設定後に、VSCodeからフォルダを開けば、即座に開発環境込みのプロジェクトのスタートです!
まとめ
VScode Remoteコンテナの機能の入門編を書いてみるにあたって、公式ドキュメントを見直しましたが…ボリュームが半端ないですね。
これをどこまで深堀りするべきか悩みましたが、入門編ということでほとんど触れないようにいたしました(笑)
本文中に飛び飛びでリンクを貼っていますが、Remote Containerの公式ヘルプは驚愕の1ページです。これだけで本書けそうなボリュームですよ・・・
というところで、今回はここまでといたします!
- 投稿日:2020-01-03T13:50:45+09:00
laravel5.4でPOSTしたページからGETのページに遷移したい
困りごと
環境面の制約があって今更Laravel5.4で色々作成しています。
文献も多いのである程度順調に作成自体は進んでいるのですが、
思わぬところで嵌って今ネットの海を迷走中です。C:\Git\php-mariadb-docker\php\www\public\qa>php artisan --version Laravel Framework 5.4.36それはタイトルにも上げた
POSTしたページからGETのページにredirectしたい
というものです。関係ありそうなところだけのrouteに加工してますが、こんな感じのroute定義
C:\Git\php-mariadb-docker\php\www\public\qa>php artisan route:list +--------+----------+-----------------------+--------------------+----------------------------------------------------------+--------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+-----------------------+--------------------+----------------------------------------------------------+--------------+ | | GET|HEAD | entry/{id} | entry.show | App\Http\Controllers\EntryController@show | web | | | PUT | entry/{id} | entry.update | App\Http\Controllers\EntryController@update | web | | | GET|HEAD | entry/{id}/edit | entry.edit | App\Http\Controllers\EntryController@edit | web | +--------+----------+-----------------------+--------------------+----------------------------------------------------------+--------------+具体的には
entry.edit(GET)からentry.updateへPOSTします。
entry.updateではDBへの登録処理を行った後に、entry.show(GET)ページにリダイレクトさせたい
という事になります。entry.updateのソースはこんな感じ
EntryController.phppublic function update(Request $request, $id){ $entry = Entry::findOrFail($id); $entry->parent = $request['parent']; $entry->name = $request['name']; $entry->department = $request['department']; $entry->email = $request['email']; $entry->tel = $request['tel']; $entry->title = $request['title']; $entry->content = $request['content']; $entry->category = $request['category']; $entry->status = $request['status']; $entry->save(); return redirect('entry.show', ['id' => $id]); }この
redirect('entry.show', ['id' => $id]);の部分、
エラーメッセージ(MethodNotAllowedHttpException)を見てるとどうやらPOSTで渡している様子これをGETで'entry.show'へリダイレクトさせる方法はありませんでしょうか?
ネットの海を徘徊しながら、コメントが付くことを祈って(笑)
宜しくお願いいたします♪
- 投稿日:2020-01-03T12:49:29+09:00
PHP if文書き方まとめ
目的
- PHPのif文の書き方をまとめる
分岐で使用する比較演算子
比較演算子 意味 A < B AがBより小さい A <= B AがB以下 A > B BがAより小さい A >= B BがA以下 A == B AがBと等しい A != B AがBと異なる 書き方の例
- elseを用いた分岐
if (条件式){ 真の時の処理; }else{ 偽の時の処理; }
- elseifを用いた分岐
if (条件式A){ 条件式Aが真の時の処理; }elseif (条件式B){ 条件式Bが真の時の処理; }else{ 条件式が偽の時の処理; }より具体的な書き方の例
- 変数
ageの値に応じて二十歳か二十歳じゃないのかを表示する処理を記載する。if ($age == 20){ echo "二十歳です"; }else{ echo "二十歳ではありません";
- 変数
ageの値に応じて二十歳か二十歳じゃないのかを表示する処理を記載する。条件式を否定する。if (!($age == 20)){ echo "二十歳ではありません"; }else{ echo "二十歳です";
- 投稿日:2020-01-03T12:48:59+09:00
phpのリクエスト情報基礎。スーパーグローバル変数の種類やセッション/クッキーの使い方をまとめてみる
- アプリケーションの全体像として、入力→処理→出力という処理の流れがある。
- 入力を受け取って、予め決められた処理を行った後、最終的な処理を出力する。
- その中で、この章では入力の部分を学んでいく
8.1 リクエスト情報
- クライアントからサーバーに送信される情報のことを、「リクエスト情報」と言う
- リクエスト情報にはテキスト文字列だけでなく、様々な情報を送り出している
- 主なリクエスト情報
- URL
- ヘッダ情報
- ブラウザがサーバーにリクエスト情報を送る際、HTTPと言うプロトコル(手続き)のルールに則って通信を行っている
- リクエストを送ると、サーバーはレスポンス情報を送り返す
- 主なレスポンス情報
- コンテンツ
- ヘッダ
- リクエストとレスポンスの情報があるので、インターネット上で情報のやり取りが行える
- 例:クライアントがサーバーに、「このURLとヘッダ情報を元に、画像データを頂戴」とリクエストする
- サーバーはクライアントに、リクエスト情報を元に、画像やヘッダなどのレスポンス情報を返す
- クライアント画面に返された画像が表示される
8.1.1 HTTP通信の確認
- Chrome標準機能のデベロッパーツールでは、以下の機能を提供している
- 現在のHTML/CSSの状態を確認
- JavaScriptのデバッグ
- ネットワークによる通信状況
- 応答時間の計測
- 今回は、ネットワークによる通信状況を確認する
- あるページを開いた状態でデベロッパーツールを起動し、そのままあるファイルへ移動すると、Networkタブではあるファイル名が表示されるので、それをクリック
- クリックすると、Headersタブに切り替わり、詳細な通信内容が表示される
- デベロッパーツールでは、以下のセクションで通信の状況がまとめられている
- General(要約)
- Response Headers(応答情報)
- Request Headers(要求情報)
HTTPプロトコルのリクエスト/レスポンス構成
- レスポンス本体はResponseタブで表示される
分類 項目 概要 リクエスト HTTPメソッド サーバーに対する直接の要求と取得するパス ^ リクエストヘッダ リクエストの構成情報、クライアント情報など ^ リクエスト本体 フォームから送信されたデータ レスポンス HTTPステータス サーバでの処理結果を表すコードとメッセージ ^ レスポンスヘッダ コンテンツの構成情報、サーバ情報 ^ レスポンス本体 コンテンツ本体 8.1.2 HTTPメソッドとHTTPステータス
- HTTPメソッドは、クライアントからサーバに対して発行する直接の命令で、以下の情報で構成される
- メソッド名
- パス
- プロトコル
- 例:GET /selfphp/chap2/hello.php HTTP/1.1
- GET メソッド名 指定したコンテンツを取得するための命令
- /selfphp/chap2/hello.php パス
- HTTP/1.1 プロトコル/バージョン
- 上記例では、HTTP 1.1 を使用して、/selfphp/chap2 フォルダ配下のhello.phpを取得しなさい、という命令を送信している
- サーバーでの処理結果を表すのは、HTTPステーターす
- 例:HTTP/1.1 200 OK
- HTTP/1.1 プロトコルとバージョン
- 200 ステータスコード
- OK ステータスメッセージ
HTTPで使用可能な主なHTTPステータス
分類 HTTPステータス 意味 100(情報) 100 Continue 継続可能 200(成功) 200 OK 成功 201 Created 成功(サーバ側に新しいリソースを生成) 202 Accepted 受付完了(未処理) 300(リダイレクト) 301 Moved Permanently リソースが恒久的に移動した 302 Found リソースが一時的に移動した 303 See Other リソースが別の場所に存在する 304 Not Modified リソースが変更されていない 400(クライアントエラー) 400 Bad Request 不正なリクエスト 401 Unauthorized HTTP認証を要求 403 Forbidden アクセスを拒否 404 Not Found リソースが見つからない 405 Method Not Allowed HTTPメソッドが不許可 407 Proxy Authentication Required プロキシで認証の必要がある 408 Request Time-out リクエストタイムアウト 500(サーバエラー) 500 Internal Server Error サーバエラー 501 Not Implemented 応答に必要な機能が未実装 503 Service Unavaliable HTTPサーバが利用不可 8.1.3 スーパーグローバル変数
- リクエストの処理方法にはスーパーグローバル変数を使う
- スーパーグローバル変数とは
- PHPが自動的にリクエスト情報などを解析し、必要な情報をスーパーグローバル変数として用意する
- どのスコープでも有効で、無条件にアクセスできる
PHPで利用可能なスーパーグローバル変数
- $_REQUEST は$_GET、$_POST/$_COOKIEの値をまとめて扱える
- しかし、次の理由から原則として利用すべきではない
- 同名のキーがあった場合に、片方のキーが上書きされてしまう
- どこから受け取ったデータなのかが曖昧になりやすい
変数名 内容 $_POST POST形式のHTMLフォームから渡された情報 $_GET クエリ情報(?? キー名 = 値)経由で渡された情報 $_FILES アップロードされたファイルに関する情報 $_SERVER リクエストヘッダ、またはサーバ固有の変数情報 $_ENV サーバ側で定義された環境変数 $_COOKIE クッキー経由で渡された情報 $_SESSION セッション経由で渡された情報 $_REQUEST $_GET、$_POST、$_COOKIEの値をまとめて処理 8.2 ポストデータ $_POST
- ポストデータとは、タグで定義されたHTMLフォームから送信されるデータのこと
- 以下のフォーム要素から入力された情報は、サブミットボタンをクリックすることでサーバに送信される
- テキストボックス
- ラジオボタン
- 選択ボタン
- method="GET"、またはmethod属性を省略したデータは、クエリ情報としてサーバに送信される
8.2.1 ポストデータを取得する
- ポストデータを送信するには、inputタグのname属性に属性値を指定し、それをformタグでmethod="POST"とし、actionの属性値に値を送信する
- 送信されたポストデータを受け取るには、actionの属性値のファイルで以下のように定義する
- $_POST['inputタグのname属性値を指定']
- クライアントから送信されたポストデータを取得するのが、$_POST(スーパーグローバル変数)の役割
- 中身は[要素名 => 値]の連想配列
- inputタグのname属性がnameである場合は、$_POST['name']でポストデータの値にアクセスできる
8.2.2 エスケープ処理の必要性
- ユーザーから入力された値やDB/外部サービス等から取得した値(動的に出力する値)を表示する場合には、HTMLエスケープと呼ばれる処理を行う必要がある
- HTMLエスケープとは
- 文字列に含まれるHTMLにおいて特殊意味を持つ文字を、それぞれ以下から以下のような文字列に置き換える処理のこと
- これを置き換えないと本来の文字(<ならタグの始まりと認識してしまうのを防ぐ)として表示できるようにする
- エスケープ処理を行わなかった場合、クロスサイトスクリプティング(XSS)と呼ばれる脆弱性の原因にもなる
- 方法として、ユーザー定義関数にエスケープ処理を行うhtmlspecialchars関数を書いて関数をいつでも呼び出せるようにする
エスケープ処理で置き換えられる例
置き換え前 置き換え後 < &It; > > & & " " エスケープ処理を行うhtmlspecialchars関数を、別ファイルにユーザー定義関数の戻り値として定義する
- 本来関数名はわかりやすくすべきだが、e関数のようにスクリプトの随所から頻繁に呼び出す場合は入力の手間を省くため短い名前にするのが吉
Encode.php<?php // 仮引数で受けとった値($str)を、エスケープ処理して呼び出し元に文字列型として返す function e(string $str, string $charset = 'UTF-8'): string { return htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, $charset); }htmlspecialchars関数の構文
- $str エンコード対象の文字列
- $style エスケープの種類
- |(パイプ)で複数種類を指定することも可能
- デフォルトのENT_COMPAT | ENT_HTML401は不完全なので、現在はENT_QUOTES | ENT_HTML5 と明示的に指定してあげるのが一般的
- $char 使用する文字エンコーディング
- バージョンによって何度か変更されているので、明示的に指定しておくのが無難
- UTF-8で設定するのが望ましい
string htmlspecialchars(string $str [, int $style = ENT_COMPAT | ENT_HTML401 [, string $char]])エスケープの種類(引数$styleの設定値)
分類 設定値 概要 クォート ENT_COMPAT ダブルクォートは変換するが、シングルクォートは変換しない ENT_QUOTES ダブルクォート/シングルクォート共に変換する ENT_NOQUOTES ダブルクォート/シングルクォート共に変換しない 無効 ENT_SUBTiTUTE 無効なシーケンスを渡した場合、代替文字で置換(デフォルトは空文字列に変換) ENT_DISALLOWED 現在の文書型で無効なシーケンスを渡した場合、代替文字で置換 ENT_IGNORE 無効なシーケンスを渡した場合、切り捨て(非推奨) 文書型 ENT_HTML 401 HTML4.01として処理 ENT_XML1 XML 1.0として処理 ENT_XHTML XHTMLとして処理 ENT_HTML5 HTML 5として処理 8.2.3 複数の値を持つ要素にアクセスする
- フォーム要素で一つの要素に複数の値を持つものをポストする場合、送信元のname属性の属性値を配列にして送信する必要がある
下記ケースだと、複数のチェックボックスにチェックが付いた場合に、受け取り側で値を複数受け取れない
- 送信元
<form method="POST" action="check2.php"> あなたがよく使用するサーバサイド技術は?<br /> <input id="php" type="checkbox" name="arch" value="PHP" /> <label for="php">PHP</label> <input id="jsp" type="checkbox" name="arch" value="JSP&サーブレット" /> <label for="jsp">JSP&サーブレット</label> <input id="asp" type="checkbox" name="arch" value="ASP.NET" /> <label for="asp">ASP.NET</label> <input type="submit" value="送信" />
- 受け取り側
<body> 選択されたのは、<?=e($_POST['arch']) ?>です。 </body>送信元で複数の値があることを明示的に指定すると上手くいく
- name属性の値を以下のように設定する
- name="属性値[]"
- 属性値の後に大括弧を付けると、同じ属性値でも配列として受け取れる
<form method="POST" action="check_multi2.php"> あなたがよく使用するサーバサイド技術は?<br /> <input id="php" type="checkbox" name="arch[]" value="PHP" /> <label for="php">PHP</label> <input id="jsp" type="checkbox" name="arch[]" value="JSP&サーブレット" /> <label for="jsp">JSP&サーブレット</label> <input id="asp" type="checkbox" name="arch[]" value="ASP.NET" /> <label for="asp">ASP.NET</label> <input type="submit" value="送信" /> </form>
- 受け取り側も、implode関数を用いて配列をカンマで連結して出力する
<body>選択されたのは、<?=e(implode(',', $_POST['arch'])) ?>です。</body>8.3 クエリ情報 $_GET
- クエリ情報とは、URL末尾の?以降に「キー名 = 値」のセットで付加される情報
- 複数のキー名がある場合は「&」で連結されている
- 例:https://search.yahoo.co.jp/search?p=PHP&aq=-1&ei=UTF-8&fr=top_ga1_so&x=wrt
- クエリ情報は、ポストデータと並んでクライアント→サーバーに情報を渡す基本的な手法
- URLに文字列として直接指定したり、タグでGETを指定した場合にもフォームに入力されたデータは全てクエリ情報としてサーバーに送信される
- method属性を省略してもフォームの内容はクエリ情報として送信される
8.3.1 クエリ情報を取得する
- クエリ送信元
<form method="GET" action="get2.php"> <label for="name">名前:</label> <input id="name" type="text" name="name" size="15" /> <input type="submit" value="送信" /> </form>
- クエリ受け取り側
- スーパーグローバル変数$_GETを用いて、$_GET[''キー名']を指定しクエリ情報を受け取る
- 受け取り側では、テキストボックスで入力された値がURLの?以降に、「キー名=値」のセットで付与される
- クエリ情報は、URL経由で送信される
- ポストメソッドで送った場合には、URL末尾のクエリ情報は付与されない
- Chrome/Firefoxなどのブラウザでは、クエリ情報の日本語はそのまま表示するが、URLコピーした場合は文字がエンコードされる
<body> こんにちは、<?=e($_GET['name']) ?>さん! </body>8.3.2 ハイパーリンク経由で値を受け渡しする
- クエリ情報はURLの末尾に直接追加することもできる
- ただし、クエリ情報には以下の文字等は直接含めることができない
- &
- #
- 空白
- マルチバイト文字
- これらがクエリ情報に含まれる可能性がある場合には、あらかじめURLエンコードしておく必要がある
- ただし、フォーム経由でクエリ情報を送信する場合はフォームの内容は自動でエンコードされるのでする必要はない
クエリ情報をURLの末尾に直接追加する具体例
- 送信元
- &や%が含まれるので、クエリ情報をURLエンコードして送信している
- エンコード処理しない場合は正しく結果を得られない
<body> <a href="link2.php?keyword=<?=urlencode('クエリ情報(&%)'); ?>"> 結果を確認</a> <!-- <a href="link2.php?keyword=クエリ情報(&%)">結果を確認</a> --> </body>
- 受け取り側
<body> <?=e($_GET['keyword']) ?> </body>urlencode関数の構文
- $str エンコード対象の文字列
string urlencode(string $str)GETメソッドでクエリ情報を送信する場合のURLエンコードをする必要性について
- &や#など、URLパラメータにおいて、特別な意味を持つ文字(&は複数のキー名の意味がある)を、通常の文字列として認識させる為に、エンコードする必要がある
- エンコードしない場合、&であればキー名の連結として認識される
8.3.3 ポストデータとクエリ情報の使い分け
- phpではポストデータとクエリ情報は$_POST、$_GET両方を用いて取得可能だが、用途に応じて明確に使い分ける必要がある
POSTデータでサーバーにリクエストした場合
- 送信前のデベロッパーツールのネットワークタブから確認
- GETとは違い、リクエストボディと呼ばれるブロックに乗せて送信している
- GETと比べるとリクエストデータはURLパラメータに付与されないが、POSTデータだからと言って、パスワードなどの秘密データは保護される訳ではない
- ブラウザの開発者ツールなどを利用すれば内容を確認できるのは簡単
- つまり、データを保護する理由でポストデータを使うのは謝り。データを保護するには、SSLのような暗号化通信を利用する必要がある
- GETとは違い、POSTデータで得た結果をブックマークすることはできない
- 理由は、URLパラメータにデータが付与されないから
- つまり、GETではレスポンスされた結果をフォーム経由せずとも直接参照できる
// HTTPメソッド Request URL: http://localhost/selfphp/chap08/post2.php Request Method: POST // リクエストヘッダ Connection: keep-alive Content-Length: 10 Content-Type: application/x-www-form-urlencoded // リクエストボディ[Form Dataタブに表示] name: 送られたクエリ情報GET(クエリ)でサーバーにリクエストした場合
- 送信前のデベロッパーツールのネットワークタブから確認
- POSTとは違い、クエリ情報がデータのURLの一部として送信している
- クエリ情報で送信できるデータサイズには限界がある
- ブラウザ/サーバー環境の制約を受ける
- IEではURLに使用できる最大文字数は2083文字
- URLの一部として送信されるクエリ情報は、パス本体との合計サイズでこの制限を超えることはできない
- Firefoxなどのブラウザではこうした制限はないが、長いURLは動作が極端に遅くなるなど問題もある
- 一般的には、クエリ情報の最大サイズは数百バイト程度にとどめておくのがよい
- クエリ情報はその性質上、ブラウザのアドレス欄にデータが露出するので、パスワードなどの秘密情報はGETを用いて受け渡しすべきではない
- 例えば、お問い合わせフォームで値を送信する場合は、機密情報の為POSTを用いる必要がある
- 検索エンジンでは、検索結果ページのURLをブックマークしているが、検索結果のページはGETメソッドを用いてURLにクエリパラメータが付与された結果をブックマークしているので、検索エンジンにブックマークして欲しい場合はPOSTではなくGETを用いて実装する
// HTTPメソッド Request URL: http://localhost/selfphp/chap08/get2.php?name=YUUKI Request Method: GET // リクエストヘッダ Host: localhost Referer: http://localhost/selfphp/chap08/get1.php Sec-Fetch-Mode: navigate // リクエストボディ[Form Dataタブに表示] name: YUUKI8.4 ヘッダ情報 $_SERVER
- ヘッダ情報とは、クライアント(ブラウザ)の種類や対応する言語、リンク元のページなど、情報が内部的に生成されており、サーバーに送信する不可視の情報のこと
- より限定して、リクエストヘッダ情報
8.4.1 ヘッダ情報の種類
- ヘッダ情報は、ポストデータやクエリ情報とは違って、データのやりとりが目には見えない
- デベロッパーツールのネットワークタブから見れる情報の中の、リクエストとレスポンスの「名前:値」の形式で書かれている HTTPメソッドやHTTPステータス以外の別の情報のことをまとめてヘッダ情報と言う
- ヘッダの種類
ヘッダ名 種類 一般ヘッダ リクエスト/レスポンス どちらも状況によっては含まれる エンティティヘッダ リクエスト/レスポンス どちらも状況によっては含まれる リクエストヘッダ リクエストのみ レスポンスヘッダ レスポンスのみ HTTP通信で利用可能な主なヘッダ
種類 ヘッダ名 概要 一般 Cache-Control キャッシュルールを規定する Connection レスポンス後にTCP接続を切断するか Date コンテンツを生成した日時 Transfer-Encoding コンテンツの転送エンコーディング方式 リクエスト Accept クライアントがサポートしているコンテンツの種類(優先順位) Acept-Language クライアントが対応している言語(優先順位) Authentication 認証情報 Cookie クライアントに保存されたクッキーデータ送信 Host 要求先のホスト名 If-modified-Since 指定された日時以降にコンテンツが更新されている場合にのみ、サーバはデータを送信 Proxy-Authorization プロキシサーバ用の認証情報 Range 要求するリソースの範囲 Referer リンク元のURI User-Agent クライアントの種類 レスポンス ETag リソースを一意に特定するためのキー情報(コンテンツが更新されていないかどうかを特定する場合などに使用) Location クライアントに新しいURIに移動するよう促す Server サーバの種類 Set-Cookie クライアントにクッキーを送信 WWW-Authenticate クライアントに認証を要求 エンティティ Content-Encoding コンテンツのエンコーディング方式 Content-Length コンテンツのサイズ Content-Type コンテンツの種類 Expires コンテンツの有効期限 Last-Modified コンテンツの最終更新年月日 8.4.2 ヘッダ情報の利用方法
- 例えば、ヘッダ情報の一つ「User-Agentヘッダ」を利用すれば、サイトにアクセスしてきたクライアント(ブラウザ)の種類がわかる
- これをアクセスログとして記録すれば、自分のページにアクセスしているユーザーの傾向を把握したり、検索エンジンのクローラの巡回頻度を知ることができる
- デスクトップと携帯端末向けブラウザを区別し、それぞれコンテンツ形式を選択して出力したりもできる
- Refererヘッダ
- 利用することで、以下が把握できる
- 自分おサイトにどのようなページからリンクが貼られているか
- 自分のサイト内でユーザがどのようにページ間を移動しているのか
- 常軌を分析することで、コンテンツそのものやサイトの構造を改善する手掛かりになる
- Accept-Languageヘッダ
- クライアントの対応言語を表す
- クライアントの言語に応じて、日本語環境のユーザには日本語を、英語環境のユーザには英語を、表示するようにできる
- 利用できるのはリクエストヘッダだけではなく、レスポンスヘッダ(代表的なものにContent-Typeヘッダ)が使える
- クライアント(ブラウザ)がコンテンツを正しく処理するのにはContent-Typeヘッダは欠かせない
8.4.3 リクエストヘッダを取得する
- リクエストヘッダの取得には、スーパーグローバル変数の$_SERVERを参照する
- $_SERVERは、リクエストヘッダの他にサーバ変数(サーバが提供する環境情報)を含む
- ヘッダ情報とサーバ変数とを識別する為に、生のヘッダ名を次の規則で整形したものを格納している
- ハイフン(-)はアンダースコア(_)に変換
- 接頭辞として「HTTP_」を付加
- User_Agentヘッダを取得するには、以下のように記述する
- $SERVER[HTTP_USERAGENT]
- ヘッダ名は全て大文字で記述する
<table border="1"> <?php // リクエストヘッダ情報を取得し、HTTP_から始まるキーとバリューを表形式で出力 foreach ($_SERVER as $key => $value) { if (mb_strpos($key, 'HTTP_') === 0) { ?> <tr valign="top"> <th><?=e($key) ?></th> <td><?=e($value) ?></td> </tr> <?php } } ?> </table>主なサーバ変数
変数名 概要 値(例) DOCUMENT_ROOT ドキュメントルート C:/xampp/htdocs GATEWAY_INTERFACE CGIのリビジョン CGI/1.1 HTTPS HTTPSプロトコルを利用しているか - PATH_INFO 実行ファイル名とクエリ文字列の間のパス情報 /wings PATH_TRANSLATED 実行中のスクリプトのファイルシステム上のパス C:\xampp\htdocs\wings PATH_SELF 実行中のスクリプト /selfphp/chap08/kenshou/req_headers2.php/wings PATH_AUTH_DIGEST HTTPダイジェスト認証時のAuthorizationヘッダの内容 - PATH_AUTH_USER HTTP認証時のユーザ名 admin_usr PATH_AUTH_PW HTTP認証時のパスワード admin_pass QUERY_STRING クエリ情報 category=PHP REMOVE_ADDR クライアントのIPアドレス 127.0.0.1 REMOVE_HOST クライアントのホスト名 localhost REMOVE_PORT クライアントのポート番号 61789 REQUEST_METHOD HTTPメソッド GET REQUEST_TIME リクエスト開始時のタイムスタンプ 1453421695 REQUEST_URI 指定されたURI \selfphp\chap08\kenshou\req_headers2.php\wings?category=PHP SCRIPT_FILENAME 実行中のスクリプト C:/xampp/htdocs/selfphp/chap08/kenshou/req_header2.php SCRIPT_NAME 実行中のスクリプト /selfphp/chap08/kenshou/req_header2.php SERVER_ADDR サーバのIPアドレス 127.0.0.1 SERVER_ADMIN サーバ管理者のメールアドレス webmaster@example.com SERVER_NAME サーバ名 localhost SERVER_PORT サーバのポート 80 SERVER_PROTOCOL プロトコル名、リビジョン HTTP/1.1 SERVER_SIGNATURE サーバのバージョン Apache/2.4.18(Win32)OpenSSL/1.0.2e PHP/7.0.1 Server at localhost Port 80 SERVER_SOFTWARE サーバのソフトウェア Apache/2.4.18(Win32) OpenSSL/1.0.2e PHP/7.0.1 8.4.4 レスポンスヘッダを設定する(1) リダイレクト
- ヘッダ情報は以下の二つに分類できる
- クライアント→サーバに送られる リクエストヘッダ
- サーバ→クライアントに送られる レスポンスヘッダ
- PHPスクリプトからレスポンスヘッダを操作することも可能
redirect.php:Locationヘッダを設定し、指定されたページにリダイレクト
- レスポンスヘッダをheader関数を用いて設定し、指定したパスにリダイレクトさせる
- Locationヘッダを、指定したパスに置き換えている
<?php // req_headers.phpにリダイレクトする header('Location: http://localhost/selfphp/chap08/req_headers.php'); // header('Location: http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/req_headers.php');header関数の構文
- $head ヘッダ文字列(「ヘッダ名:値」の形式)
- $rep 同盟のヘッダが出力済みである場合に置き換えるか
- $code HTTP応答ステータス
void header(string $head [, bool $rep = true [, int $code ]])
- Locationヘッダはリダイレクト先のURIを表すためのレスポンスヘッダ
- レスポンスヘッダを一つ送信し、リダイレクトさせている
リダイレクト元とリダイレクト先のレスポンスヘッダをデベロッパーツールで確認
リダイレクト元
Connection: Keep-Alive Content-Length: 0 Content-Type: text/html; charset=UTF-8 Date: Mon, 25 Nov 2019 18:08:23 GMT Keep-Alive: timeout=5, max=100 Location: http://localhost/selfphp/chap08/req_headers.php # ?Locationヘッダをリダイレクト先のパスに変更している Server: Apache/2.4.18 (Unix) OpenSSL/1.0.2e PHP/7.0.1 mod_perl/2.0.8-dev Perl/v5.16.3 X-Powered-By: PHP/7.0.1リダイレクト先
Connection: Keep-Alive Content-Length: 1299 Content-Type: text/html; charset=UTF-8 Date: Mon, 25 Nov 2019 18:08:24 GMT Keep-Alive: timeout=5, max=99 Server: Apache/2.4.18 (Unix) OpenSSL/1.0.2e PHP/7.0.1 mod_perl/2.0.8-dev Perl/v5.16.3 X-Powered-By: PHP/7.0.1つまり、サーバはクライアントに対してリダイレクト先のアドレスを通知しているだけで、
あとはクライアント側でLocationヘッダで受け取ったら指定のアドレスへ自動的にリクエストを発行し、指定のパスへリダイレクトしている
(リダイレクトでは、2回のリクエスト→レスポンスがやりとりされる)
- リダイレクトは、HTTPの観点では「2回のリクエストが自動的に行われている」
リダイレクト時は省略できるパスは$_SERVERを用いて取得した方が可搬性が高い
- HTTP_HOSTはサーバのホスト名、PHP_SELFは実行中のスクリプトのパスを表す
- dirnameはパスから親フォルダのパスを取り出すための関数
絶対URIの組み方
- ファイル名を変えるだけでそのまま使える
header('Location: http://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/req_headers.php');header関数には、SEOの観点からステータスコードを指定する
- header関数の第三引数にはステータスコードを指定できるが、デフォルトでは302となっている
- しかし、HTTPの規約では、302はリダイレクト時にHTTPメソッドを変更してはならない(POSTではあればPOSTでリダイレクトしなければならない)
- このままでも動作はするが、正確には303を指定
- 古いアドレスから新しいアドレスにユーザーを移動するような用途(サイトの引越しなど)では、301を利用
- 検索エンジンのクローラーは301だとリダイレクト先のアドレスをあくまで一時的なものとみなし記録しないが、301であれば恒久的な移動を意味するため、移動先のアドレスを新しいアドレスとしてクローラーが記録してくれる
8.4.5 レスポンスヘッダを設定する その他の用法
- header関数の役割はリダイレクト処理だけではない
- ブラウザに対して様々な指示を送信できる
1. キャッシュ処理を無効にする
- 以下のようなレスポンスヘッダを指定することで、ブラウザやプロキシサーバでのキャッシュ処理を強制的に無効にできる
- Cache-Control
- Expires
- Last-modified
<?php // ページの有効期限を過去の日付に設定 header('Expires: Sun, 15 Jan 1970 00:00:00 GMT'); // 最終更新日を常に更新 header('Last-Modified: ' . gmdate("D, d M Y H:i:s") . ' GMT'); // キャッシュを無効に設定 header('Cache-Control: no-store, no-cache, must-revalidate'); header('Cache-Control: post-check=0, pre-check=0', false);2. 強制的に認証ダイアログを表示させる
- HTTPステータスとして401を発行し、ブラウザで強制的に認証ダイアログを表示させることができる
認証機能の例
<?php // 認証ユーザーが送信されているかどうか if (!isset($_SERVER['PHP_AUTH_USER'])) { // 認証ダイアログを表示させる header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Basic realm="SelfPHP"'); // キャンセルボタン押下時にメッセージを出力 print 'この画面へのアクセスは認められませんでした。'; die(); } else { // ユーザー名とパスワード入力後、再度authorize.phpを呼び出し以下値を一致していれば正しく認証を行う。間違っていればその旨を出力 if ($_SERVER['PHP_AUTH_USER'] === 'admin_usr' && $_SERVER['PHP_AUTH_PW'] === 'admin_pass') { print '正しく認証が行われました。'; } else { print 'ユーザ名、またはパスワードが間違っています。'; } }3. 様々な種類のファイルを生成したい
- クライアントはContent-Typeヘッダによって、受信したコンテンツをどのように処理するかを決める
- PHPではデフォルトでtext/htmlというコンテンツタイプを受信する
- HTML以外のデータを送信する場合は、スクリプト側で明示的にContent-Typeヘッダを生成する必要がある
- 例:PDF文書を出力する場合 header('Content-Type: application/pdf');
主なコンテンツタイプ
コンテンツタイプ ファイルの種類 拡張子 application/octet-stream 任意のバイナリデータ - application/pdf PDF(Portable Document Format)文書 image/gif GIF画像 .gif image/jpeg JPEG画像 .jpeg、.jpg image/png PNG画像 .png text/css CSSスタイルシート .css text/html HTML文書 .htm、.html text/plain 普通のテキスト .txt text/xml XML文書 .xml text/xsl XSLTスタイルシート .xsl video/mpeg MPEG .mpeg、.mpg 8.5 サーバ環境変数 $_ENV
- サーバ側に設定されている環境変数を取得するためのスーパーグローバル変数
- 環境変数とは、コンピュータ上にあらかじめ定義されたパラメータのこと
- プログラムを実行する際に参照するパス、オプション値を設定する
- 例:環境変数Test /Applications/XAMPP/xamppfiles/htdocs/selfphp
- 上記の設定だと、ターミナルなどでTest と打つと、上記パスを起点に、ファイルを検索する
- デフォルトでは、$ENVは有効になっていないので、php.iniのvariablesordersパラメータを以下のように書き換える
$_ENVを有効化する為にphp.iniを書き換える
- EGPCSとは、環境変数の略
- variables_orderパラメータは、以下の順に解析する
- EGPCS変数(環境変数)
- Get(クエリ情報)
- Post(ポストデータ)
- Cookie(クッキー)
- Server(サーバ変数)
- 例えばパラメータがGPである場合、スーパーグローバル変数$_ENV、$_COOKIE、$_SERVERは生成されない
672 ; Production Value: "GPCS"; 673 ; http://php.net/variables-order 674 variables_order="GPCS" # ? variables_order = "EGPCS" に変更環境変数PATHのパスを出力する
<?php print $_ENV['PATH']; // 結果 /usr/bin:/bin:/usr/sbin:/sbin8.6 クッキー情報 $_COOKIE
- クッキーとは、クライアント側に保存可能な小さなテキストのこと
- サーバー側からクライアントに対して、自らテキストを読み書きすることができる
- クッキーの理解には、HTTPの制約を理解する
- HTTPは、クライアントの要求に対して、サーバ側がレスポンスを返すというシンプルなプロトコル
- つまり、HTTPは一回のリクエストに対して、一回のレスポンスがある為、2回目のリクエストがあったとしても、それは1回目とは無関係である
- 個々のリクエスト ?? レスポンス でワンセット
- これを、「ステートレス」(状態を保存できない)プロトコル と言う
- 状態を保存できない為、以前はページ間で情報を共有する為にクエリ情報や隠しフィールドにページ間で引き渡したい情報を保存しておくと言う方法もあった
- しかし、これは扱う情報が多くなってくるとコードが煩雑になる
- そこで、クッキーを用いて、クライアント単位で維持したい情報の管理が容易にする
8.6.1 クッキーの基本的な読み書き
- 入力フォームを作り、inputタグのvalue属性に$_COOKIEを用いて入力された値をクッキーを取得するためのキーを設定する。
- 送信先では、クッキーを保存するためsetcookieメソッドを用いて、クッキーを保存し、もう一度入力フォームの画面を見ると、保存されたクッキーを取得している。
- 入力フォームに、保存されたクッキー表示されているのが確認できる
クッキーに値を保存する方法
- クッキーを発行するのは、「setcookie関数」
- setcookie関数は全ての出力に先立って(ファイルの一番初めに)指定する必要がある
setcookie関数
以下の引数は、実質的に必要最低限設定する
- $name(クッキー名)
- $value(値)
- $expire(有効期限)
- time関数などを用いて、クッキーを保存している期間を設定する。
- time関数を用いていない場合、基点は1970年1月1日00:00:00
- 省略した場合、クッキーはブラウザを閉じたタイミングで破棄される
- 即座に破棄したい場合には、 time() - 3600 のように過去のタイムスタンプを設定する
bool setcookie(string $name [, string $value] [, int $expire = 0[, string $path [, string $domain [, bool $secure = false [, bool $httponly = false]]]]])setcookie関数の引数の実質的なオプションについて
1. $domain(有効なドメイン)、$path(パス)
- クッキーの有効範囲を指定する
- 例:$domain = .wings.msg.to と指定すると、このアドレスに関する全てのサブドメインでクッキーを有効化する
- www.wings.msn.to
- www2.wings.msn.to
- $pathも同様
- 例:$path = /selfphp/ と指定すると、このパスとパス以下のサブフォルダに対して、クッキーが有効化される
- デフォルトは、それぞれ現在のドメインとカレントディレクトリ
2. $secure(SSL通信の要否)
- $secureをtrueとすると、SSLの環境でのみクッキーを送信する
- 通信を暗号化している場合はtrueにするのが望ましい
- デフォルトはfalse(非SSL)
3. $httponly(HTTPクッキーの有効化)
- HTTPクッキーを有効にするための設定
- HTTPクッキーとは、HTTP経由でのみアクセスできるクッキーのこと
- JS経由ではアクセスできない
- XSS脆弱性によるクッキー盗聴のリスクを軽減できる
8.6.2 クッキー授受の仕組み
- クッキーをセットしたタイミングで、デベロッパツールでレスポンスヘッダの情報を見てみる
- すると、Set-Cookieと言うヘッダが含まれている
- このように、phpはレスポンスヘッダを介して、クライアントにクッキーを送信している
- クライアント側でSet-Cookieヘッダを受け取ると、自分のマシンにファイルとして記録する(Cookiesタブ→ファイル名から確認できる)
- ブラウザに保存されている全てのクッキーを確認したい場合は、「設定→コンテンツ設定→すべてのCookieとサイトデータ」を選択
Connection: Keep-Alive Content-Length: 167 Content-Type: text/html; charset=UTF-8 Date: Tue, 26 Nov 2019 03:34:19 GMT Keep-Alive: timeout=5, max=100 Server: Apache/2.4.18 (Unix) OpenSSL/1.0.2e PHP/7.0.1 mod_perl/2.0.8-dev Perl/v5.16.3 Set-Cookie: email=mailaddress%40gmail.com; expires=Mon, 24-Feb-2020 03:34:19 GMT; Max-Age=7776000 # Set-Cookieと言うヘッダが含まれている X-Powered-By: PHP/7.0.1リクエスト時に戻り、リクエストヘッダを確認する
- クッキーを保持したクライアントは、該当サーバに対してリクエストするタイミングで、常にクッキーをサーバに送信している
- ここでemailと言うクッキーがリクエストされているので、サーバ側で処理され、値をvalue=でキー名に指定することで、フォームに値を出力できる(- サーバに送信されたクッキーが保存されているので)
- レスポンスヘッダにCookieがある訳ではないので注意(リクエスト→レスポンスで受け取った値を出力しているのではなく、クッキーという箱に保存された値を参照している)
Cookie: email=mailaddress%40gmail.com; PHPSESSID=ebgvhcvdoc04u9cq0k67n8fg52 #Cookieヘッダが追加されている Host: localhost8.7 セッション情報 $_SESSION
- クッキーにはいくつかの問題点がある
- データがクライアント側で保存される
- クライアント側でクッキーを受け入れないように設定したり、クッキーをクライアント側で改ざん、削除したりできる
- つまり、クッキーによって得られたデータを元に処理を制御するのは危険が伴う場合がある
- また、クッキーは実データがネットワーク上に流れる為、ネットワーク上でリクエスト情報をロギングされた場合、意図に関わらずクッキーの内容が盗聴されてしまう
- 要は、セキュリティホールの一因となり好ましくない
- ユーザーがブラウザを開いている時だけ情報を利用したい場合は、「セッション」を使う
- 要は、一時的な情報をアプリケーション内で受け渡したい場合は、クッキーよりもセキュアなセッションを使うべきということ
8.7.1 基本的なセッション情報の読み書き
- セッションはクッキーと違い、保存したセッション情報はブラウザを一度閉じると消去される
- セッションの読み書きは、変数$SESSIONに対して値を設定/参照する要領で行える
- クッキーとは違い、secookieのような関数を使う必要はない
- セッションを利用するには、sessionstart関数でセッションを開始しておく必要がある
- session_start関数は、ブラウザデータを出力する前に呼び出すsession_start関数の構文
- $options 動作オプション
bool session_start([array $options])8.7.2 セッションの仕組み
- セッションの情報をデベロッパーツールのNetworkタブからレスポンスヘッダで確認
- PHPSEESIDがセッションIDのことである(クライアントを識別する為のコード)
- セッション情報はサーバ側で管理されるもので、クライアント→サーバ間でやり取りされるのは、キーだけ。
- クライアント単位に発行されるキーが、セッションIDである
- サーバ側は、クライアントから送信されたセッションIDをキーにして、アクセスしてきたユーザーを識別、対応するセッション情報を取得できる
- つまり、リクエストしてサーバから発行されたセッションIDを元に、2回目以降はセッションIDをサーバにリクエストし、サーバはセッションIDをキーとしてセッション情報を取得し、クライアントにレスポンスする
Server: Apache/2.4.18 (Unix) OpenSSL/1.0.2e PHP/7.0.1 mod_perl/2.0.8-dev Perl/v5.16.3 Set-Cookie: PHPSESSID=bve1b0i5l2av99c6siuaed6551; path=/ # セッションIDである
- 画面をリロードすると、リクエストヘッダに以下のCookieヘッダが表示される
Connection: keep-alive Cookie: PHPSESSID=bve1b0i5l2av99c6siuaed6551セッションとクッキーの違い
- セッションは、 データがサーバ側で管理される
- クライアント/サーバ間では、セッションIDのみ渡される
- クッキーは、データはクライアント側で保存される
- セッションは、ネットワーク上を実データが流れない(クッキーは流れる。なぜなら、保存されたクライアント側のクッキー情報をサーバ側に毎回リクエストしているから)
したがって、セッションはデータがクライアントに改ざんされたり、削除されたりする可能性は原則としてない
セッションIDを入手できれば、なりますしは可能だが、セッションid自体が一時的に生成されるID値なので、リアルタイムで盗聴を行ってない限りなりすましは難しいsessionIDの再作成
- session_regenerate_id関数を利用する
- 引数のtrueは、古いSESSIONを削除することを意味する
session_regenerate_id(true)8.7.3 セッションを破棄する
- セッションはサーバ側で保存されるので、サーバ側に負荷を掛けやすい
- セキュリティ(セッションIDを盗んだなりすましなど)という観点からも、サーバに保存されたセッションは迅速かつ確実に破棄すべき
- クッキーには次のような制限がある
- 一つのポスト、ドメイン当たりの最大個数は20個
- クッキー一つ当たりの最大サイズは4096バイト
1. セッションを明示的に破棄する
- sessionを確実に迅速に削除するには、以下をまとめて行う
- session_destroy関数を使って、セッション情報を削除する
- セッションを保存したファイルを削除する
- セッション変数を空にする
- $_SESSION = []
- セッションクッキー(IDを受け渡しの為のクッキー)を破棄する
- session_get_cokki_params()関数を用いて、セッションクッキー情報を取得し、取得したセッションクッキーをsetcookieの引数に渡す
<?php session_start(); // セッション変数を空にする $_SESSION = []; // セッションIDを受け渡す為のクッキー(セッションクッキー)を削除する if (isset($_COOKIE[session_name()])) { // セッションクッキー情報を取得する $cparam = session_get_cookie_params(); // クッキーを初期化する setcookie( session_name(), '', time() - 3600, $cparam['path'], $cparam['domain'], $cparam['secure'], $cparam['httponly'] ); } // セッションを保存したファイルを削除する session_destroy(); // セッションクッキーが破棄されたか確認 print_r($cparam);2. セッションの有効期限を設定する session.gc_xxxxxパラメータ
- 正規の手続きを行わずに他のサイトに移動した場合、セッション情報は残る可能性があるが、ガベージコレクタがある有効期限を元にセッションを破棄する
- セッションの有効期限を決めるのは、php.iniのパラメータのsession.gc_xxxxxパラメータ
- デフォルトは24分なので、24分以上アクセスがなかった場合はセッション情報はゴミと見なされる
- ガベージコレクタの実行確率は、session.gc_probability、session.gc_divisorパラメータによって決定する
- デフォルトは100分の1
- セッションの有効期限はその時々の用途/環境に応じて変える必要があるが、特別な意図がなければデフォルトでOK
8.7.4 php.iniのセッションにかかわる諸設定
php.iniにあるセッションの挙動を制御する為のパラメータをまとめる
- php.7以降では、session_start()の引数に、php.iniにある動作パラメータを連想配列として設定できるようになった
- 設定する場合は、session.を取り除いて設定する
- 例:session_start('gc_probability' => 1)1. session.save_path
- セッションの保存先を表すパラメータ
- ここで指定したパスが存在しないとエラーとなる
- サーバ上の他ユーザが自由にアクセスできるパスは指定しない
- 例:/tmpフォルダ
- セッションハイジャックの原因となる
2. session.cookie_
- セッションクッキーにかかわる設定
- session.cookie_lifetimeが0だと、ブラウザを閉じるまでセッションが有効であることを示す
- session.cookie_path 共有サーバを利用している場合には、他のユーザが管理するフォルにアクセスした場合にもセッションクッキーが送信されてしまうので、変更する
- 例:以下、同じドメイン名を共有している場合は、自分自身の使用しているフォルダのみ以下のように設定する
パラメータ名 概要 デフォルト値 session.cookie_lifetime 有効期限 0 session.cookie_path 有効なパス "/" session.cookie_domain 有効なドメイン ""(現在のドメイン) session.cookie_secure 暗号化通信でのみクッキーを送信するか Off session.cookie_httponly HTTPクッキーを有効にするか Off 3. session.use_only_cookies
- デフォルトはOn
- セッションIDの受け渡しにクッキーだけを使用する
- 原則としてOn。URLに埋め込まれたセッションIDなど受け渡したい時はOffにする
4. session.use_trans_sid
- デフォルトはOff
- クッキーからセッションIDを取得できなかった場合、相対パスに対してセッションIDが埋め込まれる
- セッションIDをURL経由で受け渡しするのはセキュリティ上好ましくない為、Onにはしない
5.session.entropy_xxxxx / session.hash_function
- セッションIDは第三者が推測可能だが、、なるべく推測されないように以下パラメータを意識する必要がある
パラメータ 概要 デフォルト値 session.entropy_file ID生成に利用するファイル(乱数発生源) " session.entropy_length ele2 0(無効) session.hash_function ele2 0(md5)
- session.entropy_file Linuxでは、/dev/urandomがデフォルト値
- session.entropy_length session.entroypy_lengthw指定
- session.hash_function 最低でも1,通常はsha512を指定しておくべき
例
vim
session.entropy_file = /dev/urandom(Linuxのみ)
session.entropy_length = 32
session.hash_function = sha512
変更した後にセッションIDを確認する
- 複雑になっている
Cookie: PHPSESSID=r41rn2j1feuvgnfo4pfvlq6si3mihfud92gfs1g31jdv3jhbs1lbm3kje2cnaqel2u2c6lpsv67jfkol2gvk4oahdh0l259srrggg53session.referer_check
- Refererヘッダのチェックを行う
- Refererヘッダはリンク元のアドレスを表す
- 設定することにより、指定した文字列以外のリンク元からきたアクセス時にはリクエストに含まれるセッションIDを無視する
8.8 アップロード処理の実装 $_FILES
- $_FILESハ、アップロードしたファイルに関する情報を取得する為のグローバル変数
- ファイルのアップロード機能も直接的に作成できる
8.8.1 画像ファイルのアップロード
- アップロード先のファイルのパーミッションを777(読み込み、書き込み、実行 が全てのユーザー/グループが可能)に変更しておく
8.8.2 入力フォームでの注意点
1. enctype属性を指定する
- フォームデータのエンコード形式を指定する
- アップロードの際には明示的に指定しておく必要がある。でないと、サーバ側で正しくデータを受け取ることができない
- 例:enctype="multipart/form-data"
2. ファイル入力ボックスを配置
- アップロードファイルを指定する場合は、type属性に属性値fileを指定する
- 複数ファイルをアップロードさせたい場合は、name属性の属性値の後ろに[](ブラケット)で配列を指定し、multiple属性を付与する
- 例:
3. アップロードファイルの上限を設定
- name属性にmax_file_size属性値を与えて、value=""で上限ファイルサイズを決めことができる
- file属性を付与したinputタグの前に書かなければならない
- 例:
- このような画面に表示する必要のない内容は隠しフィールド(type="hidden")とするのが一般的
8.8.3 アップロードの実処理
- スーパーグローバル変数$_FILESは、二次元配列の構造を持つ
$_FILESの構文
- 要素名は、タグのname属性で指定された値
$_FILES['要素名']['情報名']$_FILESの情報名に指定できる(取得可能な)値
情報名 概要 name オリジナルのファイル名 type アップロードファイルのコンテンツタイプ size アップロードファイルのデータサイズ(バイト) tmp_name サーバ上に仮保存されたときの一時ファイル名 error アップロード時に発生したエラーのコード(主なコードは次のとおり) 定数 UPLOAD_ERR_OK UPLOAD_ERR_INI_SIZE UPLOAD_ERR_FORM_SIZE UPLOAD_ERR_PARTIAL UPLOAD_ERR_NO_FILE UPLOAD_ERR_NO_TMP_DIR UPLOAD_ERR_CANT_WRITE UPLOAD_ERR_EXTENSION ファイル保存の流れ
- name属性で指定した属性値を元に、$_FILES['name属性値']['一時的なファイルのパスの変数']を指定して受け取る
// name属性値を受け取り、オリジナルのファイル名の取得と、tmp_nameとnameで一時的なファイルの保存先を取得 $src = $_FILES['upfile']['tmp_name']; $dest = $_FILES['upfile']['name'];
- move_uploaded_file関数を用いて、一時フォルダから本来の保存先にファイルを移動する
// $srcで一時ファイルのパス、$destで保存先のパスを指定 戻り値がfalseであればエラーメッセージを$err_msgに代入 if (!move_uploaded_file($src, 'doc/'.$dest)) { $err_msg = 'アップロード処理に失敗しました。'; } }move_uploaded_file関数の構文
- $file 一時的なファイルのパス
- $dest 保存先のパス
bool move_uploaded_file(string $file, string $dest)複数ファイルアップロードした場合
$_FILESの中身は以下のようになる
<?php $test = ['upfile' => [ 'name' => [ 'wings.jpg', 'webdeli.gif', ], 'type' => [ 'image/jpeg', 'image/gif', ], ]]; Array ( [upfile] => Array ( [name] => Array ( [0] => wings.jpg [1] => webdeli.gif ) [type] => Array ( [0] => image/jpeg [1] => image/gif ) ) )8.8.4 アップロードのエラー処理
1. アップロード処理そのものの成否を確認
// 受け取ったファイルがエラーであれば、エラーメッセージを$msgにそれぞれ代入し、最終的に$err_msgに代入 if ($_FILES['upfile']['error'] !== UPLOAD_ERR_OK) { $msg = [ UPLOAD_ERR_INI_SIZE => 'php.iniのupload_max_filesize制限を越えています。', UPLOAD_ERR_FORM_SIZE => 'HTMLのMAX_FILE_SIZE 制限を越えています。', UPLOAD_ERR_PARTIAL => 'ファイルが一部しかアップロードされていません。', UPLOAD_ERR_NO_FILE => 'ファイルはアップロードされませんでした。', UPLOAD_ERR_NO_TMP_DIR => '一時保存フォルダが存在しません。', UPLOAD_ERR_CANT_WRITE => 'ディスクへの書き込みに失敗しました。', UPLOAD_ERR_EXTENSION => '拡張モジュールによってアップロードが中断されました。' ]; $err_msg = $msg[$_FILES['upfile']['error']];2. アップロードファイル種類の確認
// ファイルの拡張子をチェックする為に、実ファイルのパスと許可する拡張子を変数に代入 $ext = pathinfo($_FILES['upfile']['name']); $perm = ['gif', 'jpg', 'jpeg', 'png'];3. 画像ファイルであることの確認
// 実ファイルのパスの拡張子(extension)が、画像ファイルであるかどうかチェックする } elseif (!in_array(strtolower($ext['extension']), $perm)) { $err_msg = '画像以外のファイルはアップロードできません。'; // アップロードファイルの中身が画像かどうか判定(エラーメッセージが出ないよう先頭に@を付与) } elseif (!@getimagesize($_FILES['upfile']['tmp_name'])) { $err_msg = 'ファイルの内容が画像ではありません。'; }8.8.5 php.iniによるアップロードに関連する設定パラメータ
- アップロード処理が上手くいかない場合、php.iniの以下のアップロードの挙動制御のパラメータを確認する
アップロード関連の設定パラメータ
- ファイルサイズの制限は、以下の大小で値を設定する
- memory_limit > post_max_size > upload_max_filesize
- アップロード処理が不要な場合は、file_uploadsをOffに設定する
パラメータ名 概要 デフォルト値 file_uploads アップロードを有効にするか On max_file_uploads 同時にアップロードできるファイルの最大数 20 upload_max_filesize アップロード可能な最大サイズ 2M upload_tmp_dir アップロードファイルを一時的に保存するためのフォルダ - post_max_size ポストデータとして送信可能なサイズ 8M memory_limit 利用可能なメモリサイズ 128M max_execution_time スクリプトの最大実行時間(秒) 30 max_input_time スクリプトが入力処理に利用できる最大時間(秒) -1
- 投稿日:2020-01-03T12:47:20+09:00
phpのクラスライブラリまとめ。オブジェクト指向からComposerの使い方など
標準クラスライブラリ
7.1 オブジェクト指向プログラミングの基本
オブジェクト指向プログラミングとは
- プログラム上で扱う対象をオブジェクトに見立てて、オブジェクト中心にプログラムを組み立てていく手法のこと
- phpでは、オブジェクト指向プログラミングの後盛に伴い、phpでもオブジェクト指向プログラミングに対応した、オブジェクト指向型のクラスライブラリが多く提供されるようになっている
- この章では、以下の順に学ぶ
- オブジェクト指向プログラミングの基礎
- phpの標準クラスライブラリ(定義済のクラス)の使い方
7.1.1 オブジェクト指向プログラミングの基本
- 先にクラスとオブジェクトの概念を学ぶ
- クラスとは、様々な機能が詰まった道具箱
- 関数の集合体と言っても良い
クラスと関数/定数
- クラスと関数の違い
- データの持ち方が違う
- クラスはデータを保持できるが、関数はデータを保持できない
- 関数は与えられたデータ(引数)に対して、処理結果を戻り値として返すことができるが、データは関数を通過していくだけで保持はできない
- クラスは自分自身でデータを持つことができる
- 処理前、または処理後のデータを保存して起き、必要に応じて別の用途で利用できる
- クラスとは、データを操作する為の様々な機能(関数)を備えた高機能な変数と言い換えても良い
- クラスを利用することで、関係するデータ(変数)とデータ(関数)を操作する為の手続きとを、ひとまとめにして管理できる
データを保持できる データを処理できる 変数 ? × 関数 × ? クラス/オブジェクト ? ? 7.1.2クラスとオブジェクトの関係
- クラスとオブジェクトは本質的には概念が異なる
- クラス = モノ作りを表す「設計図」
- 同一のクラスは存在しない
- オブジェクト = 設計図を元に作られた実際のモノ
- 同一のオブジェクトは存在する可能性がある
クラスとオブジェクトという概念の必要性
- クラスを実際に使う場合は、オリジナルの設計図(クラス)には手を加えず、クラスを元にできたコピー(オブジェクト)に対して操作する
- もしクラスに対して手を加えると、クラスに対して複数の習性があった場合に元の設計図(クラス)が壊れてしまうから
- この考え方は、データ型と実際の値(リテラル)との関係性に似ている
- 例えば整数型はあくまで整数という概念で、そのまま利用しない
- 実際に利用するには、整数型に沿って作られたリテラル(実際の値)を作る
- クラスはデータ型、オブジェクトは実際の値、とも取れる
7.1.3 インスタンス化とメンバの呼び出し
- クラスを元にして、コピーを作ることを「インスタンス化」、インスタンス化によってできた複製のことを「インスタンス(オブジェクト)」と呼ぶ
- インスタンス化とは、「自分専用の領域を確保すること」とも言える
- 厳密には、クラスを元にインスタンス化することで「メモリ上に領域が確保される」
クラスをインスタンス化する
- 引数は、オブジェクトを初期化する為の情報(最初にセットするデータ)
- 引数が必要ない場合も、カッコは省略できない
- インスタンス化によってできたオブジェクトは$変数名に代入する
- オブジェクトが格納された変数のことを「オブジェクト変数」又は「インスタンス変数」と呼ぶ
- クラス(オブジェクト)に属する関数と変数とそれぞれ以下のように呼ぶ
- 関数 メンバ関数、又はメソッド
- 変数 メンバ変数、又はプロパティ
- オブジェクトのメソッド/プロパティは、それぞれアロー演算子を用いて、オブジェクト変数->メソッド/プロパティで呼び出し可能
インスタンス化の構文
$変数名 = new クラス名([引数, ....])オブジェクトのメソッド/プロパティ の呼び出し
//メソッド呼び出し [戻り値 = ] オブジェクト変数->メソッド名([引数 ,]) //プロパティ呼び出し オブジェクト変数->プロパティ名[= 値]7.1.4 静的プロパティ/静的メソッド
- 種類によって、オブジェクトを生成せず直接プロパティ/メソッドを呼び出せるもの
- クラスメソッド 静的メソッド
- クラスプロパティ 静的プロパティ
- 呼び出し方は、それぞれ「クラス名::メソッド/プロパティ」とする
- オブジェクト(インスタンス)経由で呼び出すプロパティ/メソッドは以下のように呼ぶ
- インスタンスメソッド
- インスタンスプロパティ
- クラスの中で定義された定数(クラス定数)にも、::演算子を利用してアクセスする
- クラス名::定数名
静的プロパティ/静的メソッドの呼び出し方
php[戻り値 = ] クラス名::メソッド名([引数, ...]) クラス名::プロパティ名[= 値]
種類 アクセス方法 インスタンスメソッド/インスタンスプロパティ ->アロー演算子 静的メソッド/静的プロパティ :: クラス内定数 :: 7.2 DateTimeクラス
- 日付/時刻の演算や整形を行うためのクラス
- 関数型のライブラリに同じような関数があるが、今回はDateTimeクラスを優先して学ぶ
7.2.1 DateTimeオブジェクトの生成
- オブジェクトを生成/初期化する様々な方法がDateTimeクラスに存在する
1.現在の日付/時刻から生成
- DateTimeオブジェクトを生成する最も簡単な方法
- 引数なしでDateTimeオブジェクトを生成した場合、現在の日時がセットされる
- formatメソッドは、DateTimeオブジェクトの内容を整形する為のメソッド
$now = new DateTime(); //DateTimeクラスからインスタンスを作成し、$nowにインスタンス変数として代入 print $now->format('Y年m月d日 H:i:s'); //インスタンス変数$nowに対して、formatメソッド(インスタンスメソッド)を呼び出し現在時刻を年月日時分秒で整形し、表示2.日付/時刻文字列から生成
- 日付/時刻文字列から、DateTimeオブジェクトを生成する
DateTimeクラス
- $time 日付/時刻文字列
- $tz タイムゾーン
new DateTime([string $time = "now [, DateTimeZone $tz]])<?php $now = new DateTime('2016/5/15 10:58:31'); //日付/時刻文字列を指定する print $now->format('Y年m月d日 H時i分'); //DateTimeクラスから作成したformatメソッド(インスタンスメソッド)を呼び出し文字列を整形して出力3.タイムゾーンを指定する
- DateTimeコンストラクタ では、第二引数にタイムゾーンを指定できる
- タイムゾーンのリストは、静的メソッド「DateTimeZone::listIdentifiers」の戻り値から確認できる
- タイムゾーンを指定する場合、DateTimeコンストラクタ の第一引数は省略できない
- 現在時刻を取得する場合、仮にnullを指定しておく
- タイムゾーンを省略した場合、php.iniのdate.timezoneパラメータの値をデフォルト値とする
それぞれのタイムゾーンを取得する
$dt1 = new DateTime(null, new DateTimeZone('Asia/Ulan_Bator')); print $dt1->format('Y年m月d日 H時i分'); print '<br />'; $dt2 = new DateTime(null, new DateTimeZone('America/Virgin')); print $dt2->format('Y年m月d日 H時i分'); print '<br />'; $dt3 = new DateTime(null, new DateTimeZone('Europe/London'));全てのタイムゾーンを取得する
// クラスメソッドlistIdentifiersから、全てのタイムゾーンを取得する $time = DateTimeZone::listIdentifiers(); print $time[280]; //Tokyoのタイムゾーン print_r($time); //全てのタイムゾーンDateTimeゾーンのデフォルト値
.//htdocs/selfphp/ini/linux/php.ini[Date] ; Defines the default timezone used by the date functions ; http://php.net/date.timezone date.timezone=Asia/Tokyo # ?ここ4.年月日、時分秒を個別に設定する
- DateTimeオブジェクトに対して、年月日、時分秒をそれぞれ整数値で指定可能
- 引数に本来の範囲を超えた数値が指定された場合にも、DateTimeオブジェクトは適切に丸める
- 例:$now->setTime(14,70,59) //15時10分59秒とみなす
setDate/setTimeメソッド
- $year 年
- $month 月
- $day 日
- $hour 時
- $minute 分
- $second 秒
DateTime DateTime::setDate(int $year, int $month, int $day) DateTime DateTime::setTime(int $hour, int $minute [, int $second])setDate/setTimeメソッドを用いて時刻を取得する例
<?php $now = new DateTime(); //DateTimeオブジェクト(クラス)からインスタンス化した物を$nowに代入 $now->setDate(2016, 6, 25); //インスタンスに対して、インスタンスメソッド(setDate)を呼び出し、実引数で年月日を渡し呼び出す $now->setTime(14, 35, 59); //インスタンスに対して、インスタンスメソッド(setTIme)を呼び出し、実引数で時分秒を渡し呼び出す print $now->format('Y??´m???d??\ H:i:s'); //インスタンスに対して、インスタンスメソッド(format)を呼び出し、実引数で値を渡し、setした値を呼び出す5.タイムスタンプ値を設定する
- タイムスタンプ(Unixタイムスタンプ)とは、日付/時刻値を1970年1月1日00時00分00秒から経過した秒数のこと。
- 整数値ではあるので、日付の加算/減算、比較などを通常の整数値と同じように行える
- 日付/時刻関数では、タイムスタンプ値をベースに様々な処理を行う
- timeは現在の日時のタイムスタンプを求める為の日付/時刻関数
<?php $now = new DateTime(); //DateTimeクラスからインスタンスを生成し、インスタンス変数$nowに代入 $now->setTimestamp(time()); //インスタンスに対してsetTimestampインスタンスメソッドを呼び出し、引数に現在時刻を表すtimeメソッドを呼び出し、現在時刻をセットする print $now->format('Y??´m???d??\ H:i:s'); //現在時刻を年月日、時分秒で出力setTImestampメソッド
- タイムスタンプ値を返す関数からDateTImeオブジェクトを生成するのに役立つ
- $ts タイムスタンプ値
DateTime DateTime::setTimestamp(int $ts)7.2.2 日付/時刻値を指定のフォーマットで整形する - formatメソッド
- 日付/時刻値を整形する為に使う
formatメソッド
- $format 書式文字列
string DateTime::format(tring $format)書式文字列の記述子
- 書式文字列には、以下に示す記述子を含めることができる
- 記述子とは書式文字列の中で意味を持った文字のこと
| 記述子 | 概要 | 値 |
| :----- | :--------------------------- | :----------------------------------- |
| a | 午前/午後 | am | pm |
| A | 午前/午後 | AM | PM |
| d | 日 | 01 ? 31 |
| D | 曜日 | Mon ? Sun |
| F | 月(長い形式) | January ? December |
| h | 時(12時間単位) | 01 ? 12 |
| H | 時(24時間単位) | 01 ? 23 |
| g | 時(12時間単位) | 1 ? 12 |
| G | 時(24時間単位) | 1 ? 23 |
| i | 分 | 00 ? 59 |
| j | 日 | 1 ? 31 |
| I | 曜日(長い形式) | Monday ? Sunday |
| L | 閏年であるか | 0 | 1 |
| m | 月 | 01 ? 12 |
| N | 曜日 ISO-8601 形式の曜日 | 1(月曜)? 7(日曜) |
| n | 月 | 1 ? 12 |
| M | 月(省略系) | Jan ? Dec |
| r | RFC822 フォーマットの日付 | (例)Wed.21 Jac 2019 11:34:19 +0900 |
| s | 秒 | 00 ? 59 |
| S | 序数を表す接頭辞 | st | md | th |
| t | 月の日数 | 28 ? 31 |
| O | グリニッジ標準時との時差 | +0900 |
| P | グリニッジ標準時の時差 | +09:00 |
| T | タイムゾーン | (例)MDT |
| U | タイムスタンプ | - |
| u | マイクロ秒 | (例)513234 |
| W | 年の通算週 | (例)39 |
| w | 曜日 | 0(日曜)?6(土曜) |
| Y | 年(4桁) | (例)2009 |
| y | 年(2桁) | (例)09 |
| z | 年間の通算日 | 0 ? 365 |
| z | タイムゾーンのオフセット秒数 | -43200 ? 43200 |書式文字列の利用例
- DateTimeオブジェクトはそのままでは出力できず、print命令に渡す前にformatメソッドで文字列形式に変換する
$now = new DateTime(); //DateTimeオブジェクトを作成し、インスタンス変数として$nowに代入 print $now->format('Y年m月d日(D) g:i:s a'); //formatメソッドで書式を整える 年月日 時分秒 am:pm
- 記述子単体で指定もOK
- 日付/時刻値から特定の要素だけを取り出したい場合に利用できる
- 特殊な記述子として、当月の日数を求めるtや閏年であるかを判定するLなどもある
print $now->format('当月の日数:t日'); //t 月の日数 print '<br />'; print $now->format('L') ? '閏年です' : '閏年ではありません'; //閏年かどうか print '<br />';
- DateTimeクラスでは特定の書式文字列を定数としてあらかじめ公開している
- 以下がその定数一覧で、formatメソッドの引数として直接指定するが可能
定数 書式文字列 結果(例) ATOM Y-m-d\TH:i:sP 2015-12-21T14:43:12+09:00 COOKIE I, d-M-Y H:i:s T Monday,21-Dec-2015 14:43:12 JST ISO8601 Y-m-d\TH:i:sO 2015-12-21T 14:43:12+0900 RDC822 D,d M y H:i:s O Mon, 21 Dec 15 14:43:12 +0900 RFC850 I,d-M-y H:i:s T Monday, 21-DEC-15 14:43:12 JST RFC1036 D,d M y H:i:s O Mon,21 Dec 15 14:43:12 +0900 RFC1123 D,d M Y H:i:s O Mon, 21 Dec 2015 14:43:12 +0900 RFC2822 D,d M Y H:i:s O Mon, 21 Dec 2015 14:43:12 +0900 RFC3339 Y-m-d\TH:i:sP 2015-12-21T14:43:12+09:00 RSS D,d M Y H:i:s O Mon, 21 Dec 2015 14:43:12 +0900 W3C Y-m-d\TH:i:sP 2015-12-21T 14:43:12+09:00 7.2.3 日付/時刻文字列を解析する - createFromFormatメソッド
- createFromFormatメソッドは、指定した書式文字列で/日付/時刻文字列を解析し、DateTimeオブジェクトを生成する
- DateTimeクラスでは、インスタンス化の際に日付や時刻文字列を渡すことができる
- しかし、日本語混在の値を認識することはできない(2018年08月05日 など)為、そのような場合にcreateFromFormatメソッドを使う
DateTime DateTime::createFromFormat(string $format, string $time [, DateTimeZone $tz])
- $format 書式文字列
- $time 日付/時刻文字列
- $tz タイムゾーン
createFromFormatメソッドを使って日本語を交えた文字列をDateTimeクラスに渡してインスタンス化
<?php $fmt = 'Y年m月d日 H時i分s秒'; $time = '2016年08月05日 11時58分32秒'; $dt = DateTime::createFromFormat($fmt, $time); //DateTimeクラスに対して、createFromFormatクラスメソッドを呼び出し、引数に書式文字列と日付/時刻文字列を渡しDateTimeインスタンスを生成。$dtに代入 print $dt->format('Y-m-d H:i:s'); //DateTimeオブジェクトに対し、formatインスタンスメソッドを呼び出して出力する7.2.4 日付/時刻値を加算/減算する - add/subメソッド
- add/subメソッドを利用することで、日付/時刻値の加算/減算も行える
add,subメソッド
- $interval 日付/時間間隔
DateTime DateTime::add(DateInterval $interval) DateTime DateTime::sub(DateInterval $interval)DateIntervalクラス
- $spec 日付/時間を表す間隔文字列
- $specは以下のように記述する
- DateInterval('P日付間隔T時間間隔')
- 2年2ヶ月と表したい場合
- DateInterVal('P2Y2M')
- 1月5分と表したい場合
- DateInterVal('P1MT5M')
new DateInterval(string $spec)$specで表せる間隔指示子
単位 概要 Y 年 M 月 D 日 W 週(Dと一緒には利用できない) H 時間 M 分 S 秒 7.2.5 日付/時刻値の差分を取得する - diffメソッド
- diffメソッドでは日付/時刻値の差を求めることができる
diffメソッド
- diffメソッドの戻り値はDateIntervalオブジェクト。
- 内容はformatメソッドで取得する
- $dt 差分を求める日付/時刻値
- $absolute 差の絶対値を返すか
- trueに設定した場合、diffメソッドは日付/時刻値の絶対差を求める
DateInterval DateTime::diff(DateTime $dt [, bool $absolute = false])formatメソッド
- DateIntervalクラスに属するformatメソッド
- DateInterValオブジェクトの内容をformatメソッドで取得する
- $format 書式文字列
string DateInterval::format(string $format)diffメソッドを用いた例
<?php $dt1 = new DateTime('2016/5/15 10:58:31'); //DateTimeインスタンスを作成し、$dt1インスタンス変数に決め打ち年月日 時分秒を代入 $dt2 = new DateTime('2016/12/04'); //DateTimeインスタンスを作成し、$dt2インスタンス変数に決め打ち年月日 時分秒を代入 $interval = $dt1->diff($dt2, true); //$dt1と$dt2インスタンス変数の差分の絶対値を$interval変数に代入 print $interval->format('%Y年%M月%d日 %H時間%I分%S秒'); //差分の時刻をformatインスタンスメソッドで引数にある書式文字列にしたがって出力DateIntervalオブジェクトのformatメソッドで用いる記述子
- 記述子の大文字/小文字の違いは、結果が一桁の場合のみ
- 大文字は数値の先頭にゼロを加えた結果を返す
- 小文字はそのままの数値を返す
- 二桁なら変わりなし
- 日付/時刻の代償を比較する場合は、比較演算子の<,>,==を使う。
記述子 概要 %Y、%y 年 %M、%m 月 %D、%d 日 %a 総日数 %H、%h 時間 %I、%i 分 %S、%s 秒 %R、%r 負数は「-」、正数は「+」(%rでは正数は空文字) 7.2.6 phpの主要な日付/時刻関数
関数 概要 bool checkdate(int $month, int $day, int $year) 日付が妥当かを判定 string date(string $format [, int $ts]) タイムスタンプ値$tsを指定フォーマットで整形($ts 省略時は現在の日時) int time() 現在のタイムスタンプ値を取得 int mktime([int $hour [, int $minute [, int $second[, int $month [, int $year]]]]]) 現在のタイムスタンプ値を取得 int strtotime(string $time 与えられた日付文字列(英文形式)に対応するタイムスタンプ値を取得 checkdate関数の挙動
- checkdate関数が日付をチェックする基準
- 年が1?32867の範囲
- 月が1?12の範囲
- 日が指定された月の日数に含まれる(閏年にも対応)
7.3 DirectoryIteratorクラス
- DirectoryIteratorクラスは、指定されたディレクトリ配下のファイル情報にアクセスする為のクラス
フォルダの読み込み手順
- フォルダを開く
- フォルダ配下の要素を順に取得する
- ファイル情報を取得する
7.3.1 フォルダを開く
- DerectoryIteratorクラスのインスタンス化で、フォルダを開く
DirectoryIteratorクラス
- $path フォルダのパス
- 引数$pathにアクセスできない場合、DirectoryIteratorクラスはUnexpectedValueExceptionという例外エラーが発生する
- DirectoryIteratorオブジェクトを使ってファルダを読み込む
new DerectoryIterator(string $path)7.3.2 フォルダ配下の要素を順に取得する
- DirectoryIteratorオブジェクトは配列のような性質を持っている
- 引数で渡したパス以下の読み込んだファイルの内容を配列ように読み出す
- ただし、DirectoryIteratorクラスはファイル以外のサブフォルダやリンク、.(カレントフォルダ)、..(親フォルダ)も呼び出す
7.3.3 ファイル情報を取得する
取得したファイルに関する情報を取得するには、以下の表のメソッドを使う
- これらメソッドには同等の機能を提供するファイルシステム関数もある
関数名は対応するファイルシステム関数
メソッド名 関数名 概要 getATime() fileatime($path) 最終アクセス日時 getCTime() filectime($path) 作成日時 getFilename() basename($path) ファイル名 getMTime() filemtime($path) 最終更新日時 getPath() dirname($path) ファイルのパス(ファイル名を除く) getPathname() ファイルのパス(ファイル名を含む) getSize() filesize ファイルサイズ getDir() is_dir($path) ファイルがフォルダであるか isFile() is_file($path) ファイルが通常のファイルであるか isLink() is_link($path) ファイルがシンボリックリンクであるか 7.3のコード例
<!DOCTYPE html> <!-- カレントディレクトリ配下のファイルを全て取得し、ファイル名とサイズと最終アクセス日と最終更新日を表に出力 --> <html> <head> <meta charset="UTF-8" /> <title>DirectoryIterator クラス</title> </head> <body> <table border="1"> <tr> <th>ファイル</th><th>サイズ</th><th>最終アクセス日</th><th>最終更新日</th> </tr> <?php $dir = new DirectoryIterator('./'); //ディレクトリイテレータオブジェクトを作成し、同じ階層以下のファイルを読み込みインスタンス変数$dirに代入 foreach ($dir as $file) { // $dirで配列のように読み込んだファイルをforeachで回して、それぞれのファイル要素(インスタンス)に対して、isFileメソッドでファイルが通常のファイルか確認 if ($file->isFile()) { ?> <tr> <!-- 文字化けを防ぐために取得したファイル名の文字コードをUTF-8に変換 --> <td><?php print mb_convert_encoding($file->getFileName(), 'UTF-8', 'SJIS-WIN'); ?></td> <td><?php print $file->getSize(); ?>B</td> <!-- date関数を用いて日付を整形 --> <!-- 最終アクセス日時出力 --> <td><?php print date('Y/m/d H:i:s', $file->getATime()); ?></td> <!-- 最終更新日時出力 --> <td><?php print date('Y/m/d H:i:s', $file->getMTime()); ?></td> </tr> <?php } } ?> </table> </body> </html>7.4 外部ライブラリの活用 Composer
- Composerとは、パッケージ管理ツールのこと
- パッケージとは、言語に搭載されている関数やライブラリだけでなく、開発者が提供している様々な拡張ライブラリのこと
- 拡張ライブラリを使うことで、目的特化した機能を、少ないコード量で実装できる
- Composerを使うことで、これらパッケージをインストールしたり、管理できる
- 手動で管理するよりもメリットがある
パッケージ管理ツールのメリット
メリット インストール/アンインストールをコマンドひとつで実行できる
- パッケージのインストールやアップグレードを自動化で出来る
- 複雑な依存性問題を自動で解決してくれる
- 依存性とは、パッケージ動作時に別のパッケージを要求すること
- 手動で解決するのはて手間なので、Composerを利用することでインストール時に自動で解決する
- パッケージを予め定義ファイルに定義出来る
- インストールすべきパッケージがう増えた場合、まとめてインストールすることが可能
- 複数の環境で同一の環境を用意する際に便利(アプリケーションのチーム開発など)
Composerのメリット
アプリケーション単位にパッケージを管理する
一昔前のパッケージ管理ツールにPEARがある
- PEARは全パッケージをプラットフォーム単位で管理していた
- PEARのメリット
- 一度パッケージをインストールすれば、全てのアプリケーションでパッケージを共有できた
- PRARのデメリット
- 異なるアプリケーションに同じパッケージが同じバージョンで適用されてしまう
- アプリケーションが特定のバージョンに依存している場合困る(ライブラリ管理を煩わしいものにする)
Composerはパッケージはアプリ単位で管理する
- アプリごとに別のバージョンのパッケージをインストールできるため、バージョン依存の問題が解消できる
- 個別にインストールしなければならないが、コマンド一つでインストールできるのでほぼ問題になることはない
ライブラリの自動ローディング機能を利用できる
- Composerはクラスの自動ロードに対応している
- クラスライブラリを個別にインポートする必要がない
- 必要に応じて有効化できる
- プログラムから直接呼び出していないライブラリはわかりにくい。インポート忘れがちになるデメリットを払拭している(意識せずにコーディング可)
対応するライブラリが豊富
- 対応ライブラリは現在82000以上ある
- php界隈の主要なライブラリはComposer経由で提供されている
- Composer経由で提供されているパッケージリスト:https://packagist.org/
- PHPのライブラリは、C言語,PHPいずれかで記述されている
- 組み込み関数やクラスライブラリは、基本的にC言語ベースのライブラリである
- 有名なライブラリ:PECL https://pecl.php.net/
- C言語で書かれているライブラリは動作が高速、だが、ライブラリを作成したり、既存ライブラリのコードを読み解くのはハードルが高い
- 近年はPHPの性能が向上しているため、拡張ライブラリの大部分はPHPで提供されている
7.4.1 Composerのインストール(Linuxの場合)
Composerをインストールする
- インストール
curl -sS https://getcomposer.org/installer | php
- パスの通ったディレクトリに移動
$ mv composer.phar /usr/local/bin/composerパッケージ定義ファイルを作成する
アプリケーションで使用するライブラリをパッケージ定義ファイルに定義する
- 必要ライブラリが増えた際に個々にコマンド実行するのは面倒なため
パッケージ定義ファイルは、アプリケーションルート直下(現在はselfphp配下)に以下のファイル名で作成する
- composer.json
- インストールすべきライブラリは、requireキーの配下に「パッケージ名: バージョン番号」の形式で書く
- バージョンは「~バージョン番号」と書くことで曖昧に指定したり、決め打ちもできる。
- 最新バージョンインストールであれば * を書く
- 複数パッケージのインストールはその下にカンマ区切りで同じように書く
- パッケージ名はPackagistから確認できる
/Applications/XAMPP/xamppfiles/htdocs/selfphp/composer.json{ "require": { "smarty/smarty": "~3.1" //バージョン3.1以上4.0未満のsmartyというライブラリをインストール } }パッケージをインストールする(Linuxの場合)
- アプリケーションルートにて、書き込み権限を与えてからcomposerコマンドを実行する
$ chmod a+w /Applications/XAMPP/htdocs/selfphp $ composer install
- ルート配下に以下のディレクトリとファイルがあればOK
- selfphp
- composer.json パッケージ定義ファイル
- composer.lock パッケージ情報
- / vendor ライブラリの動作に必要なファイル一式
- /composer Composer共通
- /smarty Smarty関係のライブラリ
- autoload.php ライブラリを自動ロードするためのファイル
Composerでインストールしたライブラリを使用する方法
- vendor/autoload.php をインクルードする
include 'vendor/autoload.php'7.4.2 Composerで利用できる主なコマンド
1. パッケージを更新/削除する
後からライブラリを追加/削除したい場合、composer.jsonにパッケージ情報を追記し、以下のコマンドを実行
$ composer updatecomposer.jsonに後からライブラリを追加する
composer.json{ "require": { "smarty/smarty": "~3.1", "ezyang/htmlpurifier": "*" // 追加したライブラリ } }2. composer.jsonにパッケージを追加する
以下コマンド(composer require)で、composer.jsonにパッケージを追加し、そのままパッケージをインストールできる
$composer require ezyang/htmlpurifier:*3. パッケージ情報を確認する
以下コマンドで、パッケージの詳細情報を確認できる
$ composer show -i ezyang/htmlpurifier v4.12.0 Standards compliant HTML filter written in PHP smarty/smarty v3.1.34 Smarty - the compiling PHP template engineパッケージの詳細情報はパッケージ名を指定することで確認できる
$ composer show smarty/smarty-pオプション付与で、プラットフォームパッケージ(PHP拡張)を確認できる
$ composer show -p4. パッケージを検索する
composer searchコマンドで、当てはまるパッケージを検索可
$ composer search smarty smarty/smarty Smarty - the compiling PHP template engine smarty/smarty Smarty - the compiling PHP template engine slim/views Smarty and Twig View Parser package for the Slim Framework / 中略 /7.5 テンプレートエンジン Smarty
- テンプレートエンジンとは
- スクリプトから受け取った値とレイアウトイメージを結び付け、動的にページを生成する実行エンジンのこと
- phpファイルとview側(テンプレート側)を結合する為の実行エンジン
- phpファイルではプログラム側で表示に必要なデータを用意する
- テンプレート側ではデータを埋め込む場所や表示方法だけを定義する
- 以下のように呼ぶ
- テンプレート側をレイアウト
- プログラム側をビジネスロジック
- メリット
- レイアウト(ビュー)とロジック(php)を明確に分離できる
- コードを見やすくできる
- プログラマとデザイナとの分業がしやすくなる
Smartyの他にもテンプレートエンジンは存在する
- Smarty
- 古くからよく使われている
- 構文が豊富
- コンパイル式なのでパフォーマンス高い
- モジューラブルで拡張性に優れる
- Twig
- Blade
- PEAR::HTML_Template_Sigma
phpの<?php ? ?>のデリミタも、レイアウトにコードを埋め込む為一種のテンプレートエンジンと言える
- しかし、レイアウト側にレイアウトに関係のないコードが含まれることも多く、複雑なコードを生んでしまう
- テンプレートエンジンを使用することにより、シンプルに記述できるようになる
7.5.1 テンプレートエンジンの基本
1. Smartyの動作に必要なフォルダ
- テンプレートファイルを格納する為のフォルダと、コンパイル済みのテンプレートの保存先のフォルダが同じ階層に必要
- ドキュメントルート配下にテンプレートは保存しない方がいい
- 誰でも見れてしまうから
- テンプレートファイルは露出を避ける
フォルダ名 概要 templates テンプレートファイルの保存先 templates_c コンパイル済みのテンプレートの保存先 2. phpファイルを作成する
- ロジック側(php)では、テンプレートに埋め込むべき値(テンプレート変数)を準備する
require_once '../vendor/autoload.php'; //ライブラリを自動ロードする為に、指定ファイルをインポートする(smartyを使えるようにする) $s = new Smarty(); //Smartyクラスをインスタンス化し、インスタンス変数$sに代入 $s->template_dir = './templates/'; //テンプレートの保存先をインスタンスメソッドtemplate_dirに代入 $s->compile_dir = './templates_c/'; //コンパイル済のテンプレートの保存先をインスタンスメソッドcompile_dirに代入 デフォルトは./templates_c $s->assign('message', 'こんにちは、世界!'); //テンプレート変数を設定 messageというテンプレート変数の名前に対して、値を入れた状態をassingインスタンスメソッドの引数に設定 $s->display('smarty_basic.tpl'); //テンプレートファイルを指定し、displayインスタンスメソッドでテンプレートを呼び出すassingメソッド
- voidは戻り値を返さないという意味
- $varname テンプレート変数の名前
- $var 値
- 複数のテンプレート変数を設定する場合、連想配列も使える
void assing(string $varname, mixed $var)displayメソッド
- テンプレートはデフォルトで./templatesから検索される
- $template テンプレートファイル名
void display(string $template)3. テンプレートを作成する
- テンプレートフォルダの保存先はデフォルトで/templates配下。
- 拡張しは.tplとなる
- テンプレート変数を参照するには、{$変数名}のように表す
./templates/smarty_basic.tpl<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Smarty</title> </head> <body> <p>{$message}</p> </body> </html>Smartyで利用できる式表現
式 概要 {$var[0]} 配列(先頭の要素) {$var.key} 連想配列(キーがkey) {$var->name} オブジェクトのnameプロパティ {$var->hoge()} オブジェクトのhogeメソッド 4. サンプルを実行する
- 以上の準備ができたら、ロジック側のファイル、smarty_basic.phpにアクセスしてみる
- Smartyが起動していれば、無事smarty_basic.phpで指定した文字列が出力される
- templates_cフォルダ配下に、コンパイル済のテンプレートが生成されるが、今後コンパイル済のファイルを利用することで、実行効率を向上させている
- テンプレートが更新される度にコンパイルは自動で行われるので、中のファイルは弄る必要なし
Smartyの実行過程
- テンプレートファイル(.tpl)→自動コンパイル→コンパイル済ファイル(./templates_c/4823?.php)→ロード→実行
- テンプレートファイルが更新されたら、自動的に際コンパイルされる
- 2回目以降の実行では、コンパイル済ファイルを利用(実行効率向上)
7.5.2 Smartyの拡張セットアップ
- Smartyクラスには様々なプロパティが用意されている
- Smartyは手軽にカスタマイズできるようになっている
- Smarty派生クラスを自分で作り、個々のphpファイルから自分で作ったクラスにアクセスできるようにすることも可能
- パラメータ定義をひとつのファイルにまとめられる
- 個々のphpファイルがシンプルになり、変更時も自分で作った派生クラスだけ修正すればいい
Smartyクラスの拡張子を作ってみる
MySmartyクラス
<?php require_once '../vendor/autoload.php'; class MySmarty extends Smarty //Smartyクラスを継承したMySmartyクラスを作る { // MySmartyクラスからインスタンスを生成した際に自動で呼び出すメソッド public function __construct() { // 基底クラスのコンストラクタを実行(Smartyを正しく初期化する) parent::__construct(); // メソッドが呼ばれた時に、メソッド呼び出しのインスタンスに$thisを置き換える(MySmartyインスタンスであればこのインスタンスに置き換え) $this->template_dir = './templates'; $this->compile_dir = './templates_c'; $this->default_modifiers = [ 'escape:"htmlall"' ]; // アプリケーション共通で利用するテンプレート変数をアサイン(authorでYAMADA Yoshihiroを読み出す) $this->assign('author', 'YAMADA, Yoshihiro'); } // displayメソッドを元に、新たなdメソッドを準備する public function d() { // displayメソッドを、phpファイルと同名のテンプレートファイル(.tpl)を呼び出すようにしている(テンプレートファイル名を毎回明示しなくて済む) parent::display(basename($_SERVER['PHP_SELF'], '.php').'.tpl'); } }MySmartyクラスからインスタンスを生成し、ビジネスロジックを構築
// MySmartyClassを経由してautloadを読み込むことで、少ないコード量で済むようになった require_once 'MySmarty.class.php'; $s = new MySmarty(); $s->assign('message', 'こんにちは、世界!'); $s->d(); //dメソッドでdisplayメソッドを呼び出して出力Smartyクラスの主なプロパティ
分類 プロパティ 概要 デフォルト値 フォルダ $template_dir テンプレートの保存フォルダ ./templates $compile_dir コンパイルされたテンプレートの保存フォルダ ./templates_c $config_dir 設定ファイルの保存フォルダ ./configs キャッシュ $caching テンプレート出力のキャッシュを有効化/無効化 CACHING_OFF 設定値 概要 $marty::CACHING_LIFETIME_CURRENT 有効(現在時刻とキャッシュの有効期限を比較) $marty::CACHING_LIFETIME_SAVED 有効(キャッシュの生成時刻と有効期限を比較) $marty::CACHING_OFF 無効 $cache_dir キャッシュの保存フォルダ ./chche $chche_lifetime キャッシュの有効期限 3600 コンパイル $compile_check テンプレートの更新時に再コンパイル true $force_compile テンプレート実行時に毎回コンパイルするか false $default_modifiers デフォルトで適用される修飾子 [] $php_handling テンプレート内のPHPコードの扱い PHP_PASSTHRU 設定値 概要 Smarty::PHP_PASSTHRU 実行せずにそのまま出力 Smarty::PHP_QUOTE コードをHTMLエンティティとして表示 Smarty::PHP_REMOVE コードを除去 Smarty::PHP_ALLOW コードを実行 その他 $debugging デバッギングコンソールの有効化 false $error_reporting エラー通知レベル(php.iniのerror_reportingパラメータに相当) - 7.5.3 テンプレートの構成要素
- Smartyでは、テンプレート変数以外にも以下等をテンプレートで利用できる
- 演算子
- 修飾子
- 関数
修飾子(Modifier)
- 変数や関数の戻り値など、値を加工する為の仕組み
修飾子の構文
- exp 任意の式
- modifier 修飾子
- args パラメータ
- パラメータはコロン(:)区切りで複数指定可能
- {exp|modifier:'パラメータ1':'パラメータ22'}
- 修飾子はパイプ(|)で複数連結可能
- {exp|修飾子1|修飾子2:パラメータ}
- 以下の表で示したもの以外に、phpの関数を暗黙的な修飾子として利用可
- phpのnumber_format関数を修飾子として用いた例
- ({$book.price|number_format:1}円/
{exp\modifiler:args:..}Smartyで利用できる主な修飾子
分類 修飾子 概要 基本 capitalize 単語の頭文字を大文字に変換 lower 文字列を小文字に変換 upper 文字列を大文字に変換 replace:before:after 文字列beforeを文字列afterに置換 cat:str 元の値に文字列strを連結 default:def 変数が空の場合のデフォルト値を設定 truncat:num 文字列をnum桁で切り捨て count_characters 文字数をカウント 特殊文字 escape:format:enc 文字列を指定のエスケープ方式format(html、htmlall、url、hex、javascript、mailなど)でエスケープ。encは文字コード(デフォルトはUTF-8) strip 空白(スペース、改行、タブなど)を除去 strip_tags タグ文字列を除去 nl2br 改行文字を
要素に変換整形 date_format:format 日付/時刻値を書式文字列formatで整形 string_format:format 文字列をフォーマットformatで整形 関数(function)
- 主に以下の制御構文に相当する機能を提供する仕組み
- 条件分岐
- 繰り返し構文
- 他テンプレートのインポート
関数の構文
- 配下にコンテンツを持つ関数
- ブロック関数
- 持たない関数
- テンプレート関数
- テンプレート関数では、{|/func|}
- func 関数名
- attr1 属性名
- value1 値
{func attr1="value1" .....}?{/func}Smartyで利用できる主な関数
関数 概要 ^ 例 {assign} テンプレート変数を作成 {assign var="title" value="PHP" } {if} 条件式の真偽に応じて出力を分岐 {if $gender eq 'male'} 男性 {elseif $gender eq 'female' 女性 {else} その他 {/if} } {foreach} 配列/連想配列の内容を順番に処理 {foreach $fruits as $fruit} {$fruit}</p>{/foreach}
{for} 指定された回数だけループを実行(stepは増分) {for $i=1 to 10 step 2}<p>{$i}は奇数</p>{/for} {while} 条件式がtrueの間だけループを実行 {while $i It 3}<p>{$i++}</p>{/while} {include} 外部テンプレートをインクルード {include file='footer.tpl'} Smartyで利用できる主な演算子
- 演算子の部分をSmartyでは(エイリアス)として使う
演算子(エイリアス) 概要 ==(eq) 等しい !=(ne/neq) 等しくない >(gt) より大きい <(It) より小さい >=(gte/ge) 以上 <=(Ite/le) 以下 !(not) 否定 %(mod) 剰余 is[not] div by 割り切れる(割り切れない) is[not] even 偶数である(ない) is[not] odd 奇数である(ない) コメント
- コメントブロックは、以下のように指定する
- コメントブロックは、テンプレートをコンパイルする際に除去され、クライアントにも送信されない
- クライアント側にも出力したいコメントを記述する場合、HTMLの<!-- -->を利用する
- 関数や式をまとめて無効化する際にも{* ~ *}が利用できる
{* コメント内容 *}
- 投稿日:2020-01-03T12:44:50+09:00
phpのユーザー定義関数基礎メモ。無名関数やジェネレータなど使い方もまとめてみる
ユーザー定義関数とは?
- 自分で定義することができる関数のこと
- 一言でいうと、「重複したコードを一箇所にまとめる為の仕組み」
- ユーザー定義関数を用いることで、同一の処理を呼び出す時に同じコードを書く手間を省き、冗長性をなくすことができる
- コードが重複していると読みにくく、修正時に同じ処理を全て修正しなければならない為手間が増える(これを冗長化と言う)
ユーザー定義関数の基本
- function命令
function 関数名(仮引数, 仮引数2){ // 任意の処理 return 戻り値; }関数名の命名規則について
- 関数がどのような処理を行っているかわかるものにする
- 例:getTriangleArea(三角形の面積を求める関数)
- 動詞+名詞で付けると良い
- 関数名は単語の区切りを大文字で表すキャメルケース記法が一般的
仮引数とは
- ユーザー定義関数を呼び出す際に、呼び出し元からユーザー定義関数に値を引き渡す為に使う
- 複数ある場合はカンマで区切って表す
- 明示的に引数の型を指定することも可能
戻り値とは
- 関数が最終的に呼び出し元に返す値のこと
- return 命令で指定する
- return命令は関数の処理を中断させる性質がある
- 途中でreturn命令を使う場合、原則としてif / switchなどの条件分岐命令と併せて利用するべき
- 戻り値がない関数では、return命令を省略できる
- 呼び出し側の引数を「実引数」と呼ぶ
- 明示的に戻り値の型を指定することも可能
呼び出し元の関数例
$area = getTriangleArea(8,10) ⇦ 8,10が実引数(実引数を仮引数に渡して、処理させて、返ってきた値(戻り値)を受け取って呼び出す)関数の処理の流れ
1.呼び出し元の関数で実引数を指定する
2.ユーザー定義関数で仮引数を指定し、実引数の値を受け取る
3.受け取った値を関数内で処理する
4.処理した値をreturnで呼び出し元に返す
5.呼び出し元で受け取った値を、関数で実行させる型宣言
- 仮引数,戻り値,共に型宣言を行い、関数に不正な型が渡されることを未然に防ぐことが可能
- 関数では、あらかじめ型を想定して、コードを記述しているのが一般的であり、引数/戻り値には積極的に型を明示していくのが望ましい
- 型宣言はPHP 5では、タイブヒンティングと呼ばれ、以下の制限があった
- 戻り値では型宣言は利用できない
- 引数で型を宣言できるのは配列/オブジェクトだけで、intやfloatのような型は指定できない
型宣言を使った例
- 引数の型は仮引数の直前に書く
- 戻り値の型は引数リストの後方に:区切りで表す
<?php function getTriangleArea(float $base, float $height): float { return $base * $height / 2; } $area = getTriangleArea(8, 10); print "三角形の面積は{$area}です。";
- 仮引数で宣言した型は、呼び出し元の・・・・・・・・関数の実引数を最大限変換しようと試みる
- 例:仮引数で宣言した型がfloatの場合 実引数'10'→10に変換して受け取る
- 型宣言を厳密に行いたい場合、ファイルの先頭に以下のコードを追加する必要がある
- declare命令は、実行エンジンに対して、スクリプトの処理方法を指示する為の仕組み
- strict_typesディレクティブに1を与えることで、厳密な型チェックを有効にする
- declare命令は関数の呼び出し側で宣言しなければならない。つまり、型チェックの厳密さは、関数を利用する人間の意図に左右される
<?php declare(strict_types=1);型宣言で利用できる型
型名 概要 bool 真偽値 float 浮動小数点数 int 整数 string 文字列 array 配列 caliable コールバック関数 クラス/インターフェイス名 指定されたクラス/インターフェイス self 現在のクラス(メソッドでのみ有効) スクリプトの外部化
- ユーザー定義関数は通常、複数のスクリプトで共有することが多いので、再利用性を高める為に別ファイルとして保存し、必要に応じてインクルードするのが良い使い方
- インクルードの構文は以下四つの命令があるが、構文は全て同じである。最もよく使うのはrequire_once命令。
require $path include $path require_once $path include_once $path
- $path インクルードするスクリプトのファイルのパス
require、include、require_once、include_onceの違い
- 通常はrequire_once命令を使う
- 外部ファイルを読み込めないまま、以降の処理を継続できることは少ない
- 同じファイルを読み込む用途お考えにくい
- 指定したファイルがみつからなった時の挙動が違う
- - 呼び出したファイルが、スクリプトの流れの本筋に関わるのであればrequire命令を使う
- 呼び出し元と呼び出し先のファイルは、同一の階層、もしくはphp.iniのinclude_pathで設定されたパスに配置する
挙動の違いは以下
require
- 処理を中断する(Fatal Error 致命的なエラーを発生させる)
include
- 処理を継続する(Warning 警告エラーを発生させる)
require_once、include_once
- 指定したファイルを一度読み込んでいれば、再読み込みしない
- 循環呼び出しを防ぐ為に使う(呼び出し元と呼び出し先とで互いをインクルードし合うこと)
関数を定義する位置
- 基本的に関数はスクリプトのどこに定義しても正しく動作する
- 関数定義は、コンパイル時に行われるため(実行時には既にある)
- 実行時には関数は既にあるものとして、スクリプトのどこからでも呼び出すことが可能
- ただし、例外有
関数定義と呼び出し元の位置が関係する例
1.条件分岐の中で関数が定義されている場合
- 条件分岐の中で関数定義が行われている場合、条件式が評価されるまで、関数定義されない為、関数定義の後に関数呼び出しを書かないとエラーが発生する
$area = getTriangleArea(8, 10); if (true) { function getTriangleArea(float $base, float $height): float { return $base * $height / 2; } }2.関数内関数である場合
- 関数の中で関数を定義することもできる
- コンパイルの段階では、関数は登録されるのみで実行されるわけではない
- つまり、関数内関数は、実行時に親の関数が呼び出されるまで認識されない
- 下記のコードだと、関数内関数の手前で関数を呼び出している為、エラーとなる
- test関数をgetTriangleArea関数より先に呼び出せば、test関数が先に呼び出されたことにより、getTriangleArea関数もコンパイル時に登録される為、エラーが起きない
- function_existsとすることで、その関数が有効になっているかどうか調べることもできる
<?php // test(); $area = getTriangleArea(8, 10); print "三角形の面積は{$area}です。"; function test() { function getTriangleArea(float $base, float $height): float { return $base * $height / 2; } }変数の有効範囲(スコープ)
グローバル変数とローカル変数
- スコープとは、スクリプトの中での変数の有効範囲のこと
- PHPスコープは二つある
- グローバルスコープ
- スクリプト全体から参照できる
- 関数の外で宣言する
- グローバルスコープを持つ変数のことを、「グローバル変数」と呼ぶ
- ローカルスコープ
- 定義された関数の中でのみ参照できる
- 関数の中で宣言する
- ローカルスコープを持つ変数のことを、「ローカル変数」と呼ぶ
- 関数の処理が終了するタイミングで、ローカル変数は破棄されてしまう
- グローバル変数かローカル変数であるかは、「変数を定義している場所」によって変わる
- スコープの異なる変数は、名前が同一であっても違う変数と見なされる
グローバル変数とローカル変数の違いについて
- 変数を定義した箇所がグローバルスコープであればグローバル変数、ローカルスコープであればローカル変数である
- グローバル変数は関数外で宣言された変数で、スクリプト全体から参照できる変数
- ローカル変数は関数内で宣言された変数で、関数内でのみ参照できる変数
- グローバル変数もローカル変数も、名前が同一であっても別の変数として見なされる
- グローバル変数として定義された変数を関数内で処理しても、それはローカル変数の処理として見なされ、グローバル変数には影響がない
- 関数内でグローバル変数を使うには、global宣言を行う
- ローカル変数は基本的に関数の処理が終了したら、変数の値は保持されない
- 関数の処理を終了後に、ローカル変数の値を維持するには、static命令を用いて静的変数を宣言することで関数終了後も値を維持できる
関数内でグローバル変数を利用する - global命令
- 関数内(ローカルスコープ)でglobal命令を用いると、ローカル変数をグローバル変数とみなすことができる
静的変数 - static命令
- 関数内で宣言したローカル変数を、関数の終了後も維持しておく為に、ローカル変数宣言時に「static」を付ける
- 関数内でstaticを用いて宣言した変数は「静的変数」となり、関数の処理が終了しても維持される
- ただし、静的変数はあくまでローカル変数であり、関数ごと呼び出さないと静的変数の値は呼び出せない
<?php print staticTest(); //11 print staticTest(); //12 print staticTest(); //13 function staticTest(): int { static $test = 10; //静的変数として、$testの値を10に初期化する。静的変数の為、関数の処理が終了しても値は維持される return ++$test; //10をインクリメントする }インクルードファイルのスコープ
- require_onceなどの命令によって読み込まれたファイルの箇所に応じてスコープは継承される
- グローバル変数として定義されたとしても、requireの箇所が関数内であれば、元の変数はローカル変数として扱われる
- ローカル変数として定義された場合は、requireの箇所が関数外であっても、元の変数はローカル変数として扱われる(関数ごと呼び出さないと処理されない)
unset関数の挙動
- globalキーワードで定義された変数や、静的変数(static変数)を破棄する場合には、unset時の挙動が違う
- globalで定義された変数を、unsetした場合、ローカル変数としてunsetされる為、グローバル変数の値は破棄されない
- unsetした場合、以降の変数はローカル変数として扱われる
<?php $x = 10; function checkScope(): int { global $x; //グローバル変数と見なされる この時点での$xの要素値は10 unset($x); //ローカル変数として値が破棄され、以降は全てローカル変数として扱われる この時点でのローカル変数の$xの要素値は0、グローバル変数$xの要素値は10 return ++$x; //ローカル変数に対して値をインクリメントする つまり1が返される } print checkScope(); //ローカル変数1が呼び出される print '<br />'; print $x; //11が呼び出される
- 静的変数を破棄した場合、その関数での以降の処理でのみ変数を破棄する
<?php function checkStatic() { static $x = 0; //静的変数を宣言 $x++; //$xをインクリメントする print "unset {$x} "; //ローカル変数$x(1)を出力する unset($x); //ローカル変数を破棄する(静的変数は破棄されていない)これより前の処理は記憶される $x = 13; //$xに13を代入 print "unset {$x}<br />"; //ローカル変数13を出力する } checkStatic(); //unset前では1を出力する checkStatic(); //unset前では2を出力する(unset前は静的変数が破棄されていない為、以前呼び出した1が記憶された状態でインクリメントされる為)引数のさまざまな記法
引数のデフォルト値
- 仮引数に代入演算子を設定することで、デフォルト値を設定できる
- つまり、実引数を省略可能ということ
- 二つの仮引数のデフォルト値がある場合、実引数で片方だけ値を設定すると、仮引数で片方のデフォルト値のみ適用される
- 二つの仮引数のデフォルト値を設定した場合、仮引数を省略できるのは、後ろの引数のみ
- デフォルト値がない仮引数に対して、実引数は省略できない
<?php function getTriangleArea(float $base = 5, float $height) : float { return $base * $height / 2; } print getTriangleArea(10); //この場合、$baseに対して10が渡されるが、$heightに対して実引数を渡していないのでエラーとなる引数の参照渡し
- 引数は値渡しされるのが基本。
<?php fuction increment(int &$num): int { //実引数$valueを参照渡しする $num++; $return $num; } $value = 10; print increment($value); //11 参照渡しされた値を出力する print $value; //11 参照私されているので、10がインクリメントされた11が出力される可変長引数の関数
- 引数の個数があらかじめ決まっていない関数のこと
- 呼び出し元の関数が引数の個数を自由に決められる
- ユーザー定義関数として定義する場合は、仮引数で実引数を受け取る際に、配列として受け取る
- 配列として実引数を受け取る場合、...$test というように、仮引数の前にピリオドを三つ(...)付与する
- ピリオドを三つ付与した場合、戻り値は配列型となる為、そのままforeachで要素値を出力することが可能
function sum(float ...$args): float { //実引数を配列で仮引数$argsとして受け取る $result = 0; foreach($args as $arg){ //$argsの要素を一つずつ取り出し、$resultに足していく $result += $arg; } return $result; } print sum(7,3,10); //20が出力される print '<br />'; print sum(11, -5, 4, 88); //98が出力される
- php5.5以前の場合、ピリオドの三つの代わりにfunc_get_args関数を使う必要がある
function sum(){ $result = 0; $args = func_get_args(); //実引数を配列で受け取り、$argsに代入 foreach($args as $arg){ $result += $arg; } return $result; }
- 実引数の要素数を受け取るfunc_num_args関数、引数の値をインデックス番号で指定できるfunc_get_arg関数がある
- この二つの関数を利用して、forループを用いて可変長引数の関数を表す
<?php function sum() { $result = 0; for ($i = 0; $i < func_num_args(); $i++) { //func_num_argsで実引数の要素数を配列で受け取り、要素数分forループする $result += func_get_arg($i); //現在の引数の値をfunc_get_argで取得し、$i番目の値を$resultに足していく } return $result; } print sum(7, 3, 10); print '<br />'; print sum(11, -5, 4, 88);
戻り値のデータ型 関数名 array func_get_args() int func_num_gets() mixed func_get_arg(int $num)
- $num 取得する引数の位置(引数の位置は0スタート)
可変長引数と通常の引数の混在
- 通常の引数と、可変長引数が混在している場合
- 可変長引数は引数リストの末尾に必ず置く
- 末尾でなければ、全ての引数が可変長引数に没収されてしまい、以降の引数が無意味になってしまう
- 可変長引数はコードの可読性が低くなりがちなので、やむ得ない場合にのみ使用すると良い
<?php function replaceContents(string $path, string ...$args): string //$pathで通常の実引数を受け取り、$argsで配列として受け取る { $data = file_get_contents($path); //実引数を受け取った$pathに指定されたファイル(data.dat)の文字列を全て$dataに代入 for ($i = 0; $i < count($args); $i++) { //実引数の配列の要素数未満の数だけ、以下の処理を行う $data = str_replace('{'.($i).'}', $args[$i], $data); //data.datにある文字列の中から、0,1,2に当てはまる文字列を全て$args配列のインデックス番号の要素値(仮引数で受け取った実引数)に変換 } return $data; //変換した値を、呼び出し元に返す } print replaceContents('data.dat', '鈴木太郎', '2016年5月1日'); //第一引数で指定したファイルを読み込み、ファイル先で指定されたプレイスホルダを第二引数、第三引数の値に変換する...演算子による引数のアンパック
- ...演算子は実引数に指定も可能
- その場合、受け取りが側で個々の要素に分解された値を持つことができる
<?php function getTriangleArea(float $base, float $height): float { return $base * $height / 2; } // print getTriangleArea([10, 5]); print getTriangleArea(...[10, 5]); //25が出力される 実引数の指定に可変長引数を指定することで、配列を個々の値に展開できる関数呼び出しと戻り値
複数の戻り値
- 関数から複数の値を返す場合、配列やオブジェクトとして値を一つにまとめた上で戻り値を返す
- 例えば、引数の値から最大値を返すmax関数と、最小値を返すmin関数を配列として返す
// returns.php <?php function max_min(float ...$args): array { return [max($args), min($args)]; //$argsで実引数の値を配列として受け取り、要素の値から最大値と最小値を崇徳し、配列として返す }// returns_get.php <pre> <?php require_once 'returns.php'; $result = max_min(10, 2, -5, 3, 78); //実引数を配列で与える print_r($result); //配列を表示 list($max, $min) = max_min(10, 2, -5, 3, 78); //関数から返された配列の要素を、list関数を用いて各要素に振り分ける print "最大値:{$max}、最小値:{$min}"; //各要素を出力再帰関数
- 再帰関数とは、ある関数が自分自身を呼び出すこと
- 階乗計算のような同種の手続きを繰り返し呼び出すような処理を、短いコードで処理できる
- 条件分岐を併用し、ある一定の値になるまで再帰関数で繰り返し受けとった値を引いて、条件が満たされれば値を再びreturnするのが基本(戻り値がないと永遠に再帰関数が呼び出されてしまう為)
<?php function factorial(int $num): int { if ($num !== 0) { return $num * factorial($num - 1); } return 1; } print factorial(5); //$num(仮引数で受け取った実引数)が5であれば、再帰関数で右記のような計算になる 5 * 4 * 3 * 2 * 1 * 1 = 120がreturnされる可変関数
- 変数名($)の形式で呼び出せる関数のこと
- 変数によって動的に呼び出す関数を振り分けることができる
- 引数を経由して、実行する関数を決めることができる
高階関数
- 関数そのものを引数として渡したり、戻り値として返したりするもの
<?php function my_array_walk(array $array, callable $func) // 呼び出される関数を定義し、第一仮引数は配列で実引数を受け取り、第二仮引数はコールバック型(呼び出先(my_arra_walk)の関数の中で呼び出される)を定義し、showItem関数で処理された値を受け取る { foreach ($array as $key => $value) { $func($value, $key); //実引数$keyと$valueをshowItem関数に渡し、出力結果を受け取る } } function showItem($value, $key) //$funcで指定されたユーザー定義関数showItemを定義し、$funcという呼び出し元から仮引数$valueと$keyで値を受け取って処理する { print "{$key}:{$value}<br />"; //$funcに出力結果を返す } $data = ['杉山', '長田', '杉沼', '和田', '土井']; //配列を宣言し、値を格納 my_array_walk($data, 'showItem'); //my_array_walk関数を呼び出し、第一実引数は配列、第二実引数は関数を指定
ユーザー定義関数を自由に差し替えることが可変関数の最大の特長である
上記のshowItemをsumに変え、配列$dataの合計値を求める可変関数を作る
- sumの中身(詳細な機能の関数)だけ利用者が決め、my_array_walkは書き換えないようにすると、汎用性の高い関数を設計できるようになる
<?php function my_array_walk(array $array, callable $func) //高階関数を定義。第二引数でコールバック関数を可変関数で呼び出し { foreach ($array as $key => $value) { $func($value, $key); //可変関数を呼び出し } } $result = 0; function sum(float $value, int $key) //ユーザー定義関数を定義 { global $result; //グローバル変数を定義 $result += $value; //グローバル変数resultに、$valueを足し合わせていく } $data = [100, 50, 10, 5]; my_array_walk($data, 'sum'); print "合計値:{$result}";
- 可変関数をsumからinFuncに差し替えてみる
<?php function my_array_walk(array $array, callable $func) { foreach ($array as $key => $value) { $func($key, $value); } } $result = 0; function sum($key, $value) { global $result; $result += $value; } function showItem($key, $value) { print "${key}番目の言語は${value}です"; print "<br />"; } $array = ['10', '20', '30']; my_array_walk($array, 'showItem'); print "クラスの合計値は${result}点です"; //sumが呼び出されないので0点のままとなる無名関数(クロージャ)
- 高階関数(my_array_walkのような関数)に渡す為のユーザー定義関数(showItemやsum)は、渡すことを目的とした使い捨て関数のため、名前をつける必要はない
- そのような目的で使う関数として、無名な関数(無名関数)がある
- 「クロージャ」とも呼ばれる
- 特的の機能だけを定義したいという場合に用いる。スクリプトが読みやすくなる
- 無名関数は、「function」キーワードの後方に関数名がない
function(仮引数,){ // 任意の処理 return 戻り値; }
- 無名関数を用いた例
<?php function my_array_walk(array $array, callable $func) { foreach ($array as $key => $value) { $func($value, $key); } } $data = ['杉山', '長田', '杉沼', '和田', '土井']; my_array_walk( $data, function ($value, $key) { print "{$key}:{$value}<br />"; } ); //高階関数の呼び出し元の引数に直接無名関数を定義
- 無名関数は、変数に代入することも可能
- その場合は、ブロックの末尾に必ずセミコロンが必要
$triangle = function(float $base, float $height): float {~};親スコープの変数を引き継ぐ use命令
- 無名関数では、親スコープから変数を引き継ぐことも可能
- 親スコープから変数を引き継ぐには、use命令を用いる
<?php function my_array_walk(array $array, callable $func) { foreach ($array as $key => $value) { $func($value, $key); } } $data = [100, 50, 10, 5]; $result = 0; my_array_walk($data, function (float $value, int $key) use (&$result) { $result += $value; }); //useは親スコープ(my_array_walk関数)配下の$reusltを指し示し、その変数を参照渡しする print "合計値:{$result}";
- 以下のように関数の中に関数がある場合などは、globalではなくuseが適切
<?php function my_array_walk(array $array, callable $func) { foreach ($array as $key => $value) { $func($value, $key); } } function hoge() { $data = [100, 50, 10, 5]; $result = 0; // my_array_walk($data, function (float $value, int $key) { // global $result; //グローバル変数を指し示すので、hoge関数の中の$resultは読み込まない // $result += $value; // }); my_array_walk($data, function (float $value, int $key) use (&$result) { $result += $value; }); ////use命令は親スコープを指す為、hoge関数配下の$resultを正しく参照し、最後のhoge関数呼び出しで値が反映される print "合計値:{$result}"; } hoge();ジェネレータ
- yield命令を使って、関数の中で戻り値をその時々に応じて変えることができる関数のこと
- returnとの違い
- returnを用いて戻り値を返す場合、呼び出した後の時点で関数の処理を中断する
- yieldを用いて戻り値を返す場合、呼び出した後の時点で関数の処理を一時停止し、再び呼び出した場合に、一時停止後の位置から呼び出す
- 戻り値のデータ型は「Generator」
<?php // 呼び出すごとに、yieldで設定した戻り値を返した所で一時停止する function myGen() { yield 'あいうえお'; yield 'かきくけこ'; yield 'さしすせそ'; } // yieldで返された値を$valueで全て出力する foreach (myGen() as $value) { print $value.'<br />'; }素数を求めるジェネレータ
- 素数を求める関数 floor(sqrt(引数))
- floorは引数の値を丸め(小数点以下切り捨て)、sqrtは平方根を求める
- 割り切れるかどうか判定
- if (値 % 2 === 0)
- yield命令を用いることで、無駄なメモリ消費をせず必要に応じて値を返すことができる
- 何かしらのルールに従って値セットを生成する用途ではジェネレータを使う
<?php // 素数を求めるジェネレータ関数 function getPrimes() { $num = 2; while (true) { if (isPrime($num)) { yield $num; //値が素数であれば、値を返す } $num++; } } // 引数$valueが素数かどうかを判定 function isPrime(int $value): bool { $prime = true; // 2 ~ sqrt($value)で、$valueを割り切れる(余りが0)のものがあるか for ($i = 2; $i <= floor(sqrt($value)); $i++) { if ($value % $i === 0) { $prime = false; //割り切れるものがあれば素数ではない break; } } return $prime; //素数であればtrueを返す } // 素数を受け取り、順に出力する foreach (getPrimes() as $prime) { // 素数が101以上になったら終了する if ($prime > 100) { die(); } print $prime. ','; }ジェネレータの結果を取得する
- php7.0以降では、ジェネレータ関数でreturn命令を利用できる
- return命令はジェネレータが全ての処理を終えた時点で呼び出す
- ジェネレータ内でのreturn命令は、最終的な値を特別扱いできて、その最終的な値をgetReturn関数で取得できる
- getLines関数を用いると、GeneratorオブジェクトのgetReturnメソッドを利用することで、行数を取得できる
- ジェネレータの呼び出し側で処理の終了を検知するようなコードも簡単に記述できる
<?php function readLines(string $path) { $i = 0; $file = fopen($path, 'rb') or die('ファイルが見つかりません'); while ($line = fgets($file, 1024)) { $i++; yield $line; } fclose($file); return $i; } $gen = readLines('sample.dat'); foreach ($gen as $line) { print $line.'<br />'; //sample.datの行を全て出力する } print "{$gen->getReturn()}行ありました。"; //readLinesの引数で受け取った行数(return $i)を受け取り表示する一部の処理を他のジェネレータに委譲する
- php7.0でもう一つ yield from 命令が追加された
- yield from 命令を用いることで、ジェネレーの中で別のジェネレータや配列を呼び出して列挙できる
- 複雑なジェネレータを記述する際は、積極的に利用したい
yield from list
- list 他のジェネレータ、配列など
<?php //ジェネレータを呼び出す function readFiles(array $files) //二つのファイルを配列型で受け取る { foreach ($files as $file) { yield from readLines($file); //ジェネレータを設定する } } // 受け取った二つのファイルを処理する function readLines(string $path) { $file = fopen($path, 'rb') or die('ファイルが見つかりません'); while ($line = fgets($file, 1024)) { yield $line; //行データを一つずつ返す } fclose($file); } // readFiles関数を呼び出し、実引数で配列型で二つのファイルを設定し、readFilesに渡す foreach (readFiles(['sample.dat', 'sample2.dat']) as $line) { print $line.'<br />'; }
- 投稿日:2020-01-03T10:56:47+09:00
【AWS】Elastic BeanstalkでLinux 用 Windows サブシステムを使用せずにLaravelアプリケーションをデプロイする
Elastic BeanstalkでLinux 用 Windows サブシステムを使用せずにLaravelアプリケーションをデプロイ方法です。
本手順では、代わりに、フリーの圧縮・解凍ソフトを使用しています。環境
- PHP:バージョン7.3(Elastic Beanstalk)
- Laravel:バージョン6.9.0
- OS:Windows10(ローカル)
手順
Elastic Beanstalk への Laravel アプリケーションのデプロイ | aws
に従って実施します。
タイトルの件を以下に、補足事項としてまとめます。補足事項
手順では、Linux 用 Windows サブシステムを使用するとありますが、以下の通り実行すればインストール不要です。
1. Laravelプロジェクトの作成
ローカル環境でコマンドプロンプトを起動して、任意のパスでコマンドを実行し、Laravelプロジェクト作成します。
こちらは、手順と同じコマンドで大丈夫です。
※今回は、プロジェクト名を「testLaravelApp」としました。> composer create-project --prefer-dist laravel/laravel testLaravelApp2. zipファイルの作成
1で作成したプロジェクトを圧縮します。
Windowsにおいては、デフォルトではzipコマンドを実行できないため、GUIベースで圧縮します。
私はフリーの圧縮・解凍ソフト7-Zipを使用しました。注意点
圧縮実行時には、以下の点に気をつけてください。
1. デプロイしたいプロジェクト直下で圧縮する
公式の手順(コマンド)からもわかる通り、デプロイしたいプロジェクト直下で圧縮作業を実施します。
プロジェクトをそのまま圧縮してデプロイすると以下のようなエラーが出てしまい、ページへのアクセスもできないので要注意!ダッシュボード
ヘルス
調べたところ、対象プロジェクトをそのまま圧縮すると余計なディレクトリが含まれてしまうから、とのこと。
【参考】
Following services are not running: proxy @ AWS — after Laravel re-config | stack overflow2. vendorは対象外とする
公式の手順にも記載ありますが、念のため。
上記1, 2を考慮しての7-Zipでの圧縮は以下の通りとなります。
そうすると、同フォルダ内に対象のLaravelプロジェクトと同じ名称のzipファイルができるので、それをアップロード・デプロイすればOK!
オマケの注意点
こちらも公式手順に記載ありですが、ドキュメントのルートを
/public
とするのを忘れずに☆
無事、デプロイ→ページ表示できました!
ダッシュボード
ページ
参考
- 投稿日:2020-01-03T00:32:12+09:00
FFmpegを使ってルパン三世タイトルジェネレーターをPHPで書いてみた
新年も明けて2020年になりましたね!
話は飛びますが、年末辺りからTwitterのエンジニア界隈でルパン三世を模した動画を幾度と無くTLで見ました!
ググって同様の動画を作成するジェネレーターを幾つか確認しましたが、せっかくならPHPで動画作成したい!
ということで開発しました!
※都度、リファクタを加えながら開発してたので5時間くらいかかりましたw
※出力結果は音付きのmp4ファイルです環境
動作確認済み環境
- macOS Catalina :
ver 10.15.2- PHP :
7.3.1- Web Server :
Apache必要な外部ライブラリ
その他特記事項
- PHPにGDモジュールがインストールされていること
実行方法
FFmpegのコマンドパスが通った上記の環境下で、
lupin-generator/index.phpの
$string = "あけおめ!";を編集して実行してください!
出力先は、
lupin-generator/output/output.mp4です。
※既に出力結果が存在する場合は、上書き保存されます
設定関連
基本的に画像やフレームの作成に用いるパラメータは、
lupin-generator/Define.phpに記述しています。
以下は各定数の概要です。
定数 概要 L_FFMPEG_COMMAND_PATH FFmpegのコマンドパス L_FONT_FILE 出力する文字のフォント(TrueType)ファイルパス。デフォルトではGoogleのWebフォントを利用 L_IMG_WIDTH 出力結果の横幅 L_IMG_HEIGHT 出力結果の縦幅 L_BACKGROUND_COLOR_R 出力結果背景のR(0 〜 255) L_BACKGROUND_COLOR_G 出力結果背景のG(0 〜 255) L_BACKGROUND_COLOR_B 出力結果背景のB(0 〜 255) L_FONT_COLOR_R 出力結果文字のR(0 〜 255) L_FONT_COLOR_G 出力結果文字のG(0 〜 255) L_FONT_COLOR_B 出力結果文字のB(0 〜 255) L_FONT_SIZE 1文字専用フレームのフォントサイズ L_IMG_ANGLE 文字の角度 L_IMG_X 左上を基点としたX軸方向の開始位置 L_IMG_Y 左上を基点としたY軸方向の開始位置 L_TEMP_IMAGE_PATH フレームの作成に必要な画像ファイルの保存パス L_TEMP_FRAME_PATH 動画の作成に必要なフレームファイルの保存パス L_OUTPUT_PATH 出力結果(動画)の保存パス L_TYPE_SOUND タイピングサウンドの参照パス L_TITLE_SOUND タイトルサウンドの参照パス L_FRAME_LIST_FILE FFmpegを用いてフレームを連結する際に参照パスリストを記述するファイルのパス ギジュツ的なこと
内部仕様
- 入力文字列を1文字ずつ画像に書き出す
- 入力文字列をタイトル画像に書き出す
- 画像を1枚ずつサウンド付きのフレームに書き出す
- フレームを連結する
その他
コマンドラインから引数を投げて実行することを想定しても良かったのですが、それならそもそもPHPで書かないので直ガキ仕様にしました笑
あと、出力結果は一行で文字列を埋めるので、
\n区切りでパースしたり、形態素解析のIgo等用いて違和感無く複数行にすることも出来たのですがやってません?
他に特別なことと言えば、
- リファクタリングに専念した
くらいです?
個人でPHPのフレームワークを現在進行系で開発しているのですが、その際にCodeIgniterの本体を読み漁って覚えた立ち回り等は使ってます。
例えば、このルパン三世タイトルジェネレーターでは、
file_exists(L_FRAME_LIST_FILE) OR touch(L_FRAME_LIST_FILE);といった処理があります。
大抵どの言語でも同様の仕様だと思いますが、論理和は左側の条件式の評価が
trueであれば、右側の条件式を評価せずに返り値を
trueとします。
上記コードの場合はL_FRAME_LIST_FILEに該当ファイルが存在していれば、左側の
file_exists(L_FRAME_LIST_FILE)はtrueとなり右側の
touch(L_FRAME_LIST_FILE)は実行されません。
逆にファイルが存在しなければ、右側の式も評価されることになるので実行されます。
要するに、
if ( !file_exists(L_FRAME_LIST_FILE) ) { touch(L_FRAME_LIST_FILE) }と同様です。
あとは、マルチバイト文字も許容することになるので、それらの文字列を一文字ずつ配列に格納する処理を施しました!
$this->strings = array_values( array_filter( preg_split("//u",$this->string), "strlen" ) );上記コードですが、まずは正規表を利用して入力文字列を配列に格納しています。
preg_split("//u",$this->string)UTF-8にマッチする文字のみを検索パターンとしています。
"//u"そして、上記処理を施すと空文字のvalueが格納されたインデックスが生成されるためにarray_filterで配列を走査しています。
array_filterの第二引数に指定するコールバック関数は返り値がfalseであるインデックスを削除した配列を返却します。
strlenは文字列の長さをint型で返却するPHPの組み込み関数で、上記のようなvalueが空文字のインデックスの場合は
0を返却します。
これはPHPの公式マニュアルにある通り、int型の0をbool値として扱う場合は、
falseと評価されます。
そして、連番画像として扱っているため歯抜けのkey名をarray_valuesで整えて実装しています。
他と言えば実行演算子を覚えたくらいです?
以上です!
ありがとうございました!




















