20210912のPHPに関する記事は12件です。

Laravel 8でテキストを改行させ、JSインジェクションを実行させない方法。

<p>{!! nl2br(e($post->body)) !!}</p> e()で文字実態参照に変換。 nl2br()で改行コードを改行タグに変換。 {!! !!}でタグで改行させるようにする。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 8でテキストを改行させ、埋め込まれたJSを実行させない方法。

<p>{!! nl2br(e($post->body)) !!}</p> e()で文字実態参照に変換。 nl2br()で改行コードを改行タグに変換。 {!! !!}でbrタグで改行させるようにする。(文字実体参照への変換を無効にする)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP で定数を使ってみる

PHPで定数を使ってみた ちなみに定数とは const キーワードか、 define() 関数を使うことで、定数を宣言することが出来ます。 define() を使えば任意の式を使って定数を定義できますが、 const キーワードを使う場合には、 次の段落で説明する制約があります。 定数が一度定義されると、 変更または未定義とすることはできません。 https://www.php.net/manual/ja/language.constants.syntax.php みなさんPHPで定数って使ってますか?? 私は古いシステムを修正することが多いのですが、定数を使っているプログラムは非常に少ないです。(あくまで私の偏見ですが) 私は師匠に定数は積極的に使っていけと習ったので、プログラムの先頭には定数が並んでいる場合が多いです。 DBに保存される際にINT型で保存されるけれど、画面に出力される時は日本語で表示してね、といった文言は是非定数を使ってあらかじめ定義しておきたいですね。 例)曜日、旧暦、性別など <?php //9月12日(日)作成 const WEEK_ARRAY = array("日","月","火","水","木","金","土"); $week = date("w"); echo WEEK_ARRAY[$week]; //日 const MONTH_ARRAY = array("","睦月","如月","弥生","卯月","皐月","水無月","文月","葉月","長月","神無月","霜月","師走"); $month = date("n"); echo MONTH_ARRAY[$month]; //長月 const GENDER_ARRAY = array("その他","男","女"); $gender = 1; echo GENDER_ARRAY[$gender]; //男 ?> 上記のような例だとほぼ変わることはありませんが 例えば業務システムのステータスなどは仕様変更などで文言が変わることはよくある話だと思います。 仕様変更のたびに条件式を書いてある部分を探し出して書き直すといったことは非常にめんどくさいので、プログラムの先頭に定数で宣言しておけば修正も楽々できますね。 例)登録状況、評価表示、通貨など <?php const REGIST_STATUS_ARRAY = array("未登録","仮登録","登録済","退会済"); $register = 1; echo REGIST_STATUS_ARRAY[$register]; //仮登録 const EVAL_STATUS_ARRAY = array("","GOOD","NICE","GREAT","EXCELLENT"); $evaluation = 3; echo EVAL_STATUS_ARRAY[$evaluation]; //GREAT const MONNEY_TYPE_ARRAY = array("円","ドル","ウォン","","",""); $monney_type = 0; echo MONNEY_TYPE_ARRAY[$monney_type]; //円 ?> これらはよく仕様変更や追加項目がありそうな部類だと思いますが、こうして定数でまとめておけば急な仕様変更や追加項目があっても対応が早そうですね♪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3バケットを作成してEC2と接続する【備忘録】

はじめに  前記事(EC2サーバー構築の概要【備忘録】)で作成したEC2インスタンスと、AWSストレージサービスであるS3をPHPで接続した時に調べたことの備忘録。  AWSアカウントを作成し、EC2インスタンスのセットアップが完了していることが前提です。  具体的な概要・用語についての記事であり、具体的な手順については割愛しています。 目次 全体の流れ 項目別の詳細 yumとrpm 参考文献 全体の流れ S3セットアップ S3バケット作成 IAMロール作成 EC2インスタンスにIAMロールを割り当て エンドポイントの作成 AWS SDK for PHP でS3に接続 PHPのバージョンアップ Composerインストール AWS SDK for PHPをインストール 項目別の詳細 S3バケット作成  手順通りやればいいので割愛。 IAMロール作成・IAMロールを割り当て  IAM(Identity and Access Management)とは、AWSサービスへのアクセスを安全に行うためのAWSサービス。その機能の一つに、「限定されたアクセス権限を持つロールやユーザーの作成」がある。仮にアクセス情報が漏洩した場合、これらのIAMロール・IAMユーザーを認証に用いていれば、アクセス権限が限定されているため、被害を最小限に抑えられるという利点がある。  また、IAMユーザーとIAMロールの違いは使用方法の違いにある。  IAMユーザーは「アクセス権限を限定されたユーザー」という体でアクセスキーとシークレットキーを設定ファイルなどに記述して認証させるが、IAMロールはAWSコンソールからEC2インスタンスへの割り当て設定をすることで、いちいちキーを記述したりせず自動的に認証をさせることができる。  今回は、EC2とS3の接続にはこのIAMロールを用いた。 エンドポイントの作成  IAMの件と同様、最大限「セキュアな通信」というものを実現させるために作られたシステム。  AWSサービスの中で、EC2はVPC(仮想空間的なもの)内にあり、S3はVPC外にある。EC2がVPC外のサービスと通信をするためにはプライベートネットワークからインターネットへの接続を仲介させる必要があるが、エンドポイントを設定すると、その役割をエンドポイントが担ってくれる。これにより通信の際にインターネットを挟まずに済むため、「セキュアな通信」が可能となる。 PHPのバージョンアップ  AWSサービスとの接続にはAWS SDK for PHPを用いるが、これはPHP5.4に対応していない。しかしながら、yum install phpでPHPを入れると5.4が入ってしまう。  この問題には「パッケージリポジトリ」と「yum」の概念が関係している。  「yum install パッケージ 」は、「登録されたパッケージリポジトリ(色々なパッケージが入ってるフォルダ)から指定されたパッケージをインストールする」という指示を出す。そしてEC2にデフォルトで登録されているAmazon LinuxのパッケージリポジトリにはPHPは5.4しか入っていないため、このような不具合が起きる。  そこで、PHP7.4が入っている外部リポジトリ「remi」を登録し、そこからインストールする。 sudo amazon-linux-extras install epel //amazon linuxリポジトリにepelを追加し、yumでインストールできるようにする //epel-release:remiを入れるために必要なパッケージ sudo yum install epel-release //epel-releaseをインストール sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm //rpmコマンドでremiをインストール sudo yum install -y php74 php74-php php74-php-fpm //PHP7.4をインストール sudo ln -s /usr/bin/php74 /usr/bin/php //php74のシンボリックリンクを作成する(phpコマンドを使えるようにする) 備考:-UvhはオプションU(アップグレード),v(詳細情報),h(進行状況の表示)のまとめ書き。 Composerをインストール  AWS SDK for PHPはComposerからインストールするので、まずはComposerを入れる。  今回はcurlコマンド(ファイルを転送するコマンド。引数にURLを指定することでファイルをダウンロードできる。-sSでエラーメッセージ以外の進捗状況を非表示にする)を用いる。 curl -sS https://getcomposer.org/installer | php //Composerをインストール AWS SDK for PHPをインストール php composer.phar require aws/aws-sdk-php //AWS SDK for PHPをインストール yumとrpm  個人的に気になった部分なので調べた。  rpm(Redhat Package Manager):Red Hat系のLinuxで用いられるパッケージマネージャ。以前はパッケージを入れる際に用いられていたが、依存関係を考慮するのが厄介になってきたため、リポジトリ内の依存関係にあるパッケージをすべてインストールするyumが作られた。現在はリポジトリ単位のパッケージ(remiなど)を入れる際に使われる。  yumはrpmを基に作られているため、併用しても依存関係は崩れない。 参考文献 LaravelでAWS S3へ画像をアップロードする [AWS] VPCエンドポイント経由でEC2からS3へファイル転送 Amazon S3 におけるエンドポイント IAM ロール リポジトリ・yum・rpmについて AWS SDK For PHPを使ってみた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】三項演算子の結果を文字列結合したいが上手くいかない

PHPで、三項演算子の結果を文字列結合して表示したいことがあり、次のようにコードを書いてみて『結果:Ok』を期待したけれど、実際に出力されるのは『Ok』のみ。 「結果:」の形跡はまったくなく不思議。いったいどこにいってしまったのだろうか。 $hoge = 3; echo '結果:'. ($hoge == 3) ? 'Ok' : 'Ng'; >Ok とりあえずググってみた 「teratail」で質問と回答を見つけた。 (’答えは’.0) ? '正解!' : '間違い!'; ('答えは、0’) ? '正解!' : '間違い!'; (TRUE) ? ' 正解!' : '間違い!'; なので、三項演算子の部分を()で囲みましょう。 「三項演算子の部分を()で囲み」で解決なのだけれど、少し丁寧に理解しておこうと思う。 1つ1つ動きをみていく 読み解くには「演算子の優先順位」が分かればOKです。 echo '結果:'. ($hoge == 3) ? 'Ok' : 'Ng'; この文の演算子は、3つ 演算子 名前 . 文字列演算子 == 比較演算子 ? : 三項演算子 優先順位は、上から順に、. > == > ? : となる。 でも括弧で囲われているところが一番最初に評価される。 ($hoge == 3) は、true だ。 echo '結果:'.true ? 'Ok' : 'Ng'; でお次は、三項演算子より文字列演算子の方が、優先順位の高いので、'結果:'.true の文字列結合が行われる。 true の型変換が起こって、'結果:1' になるのかな。 echo '結果:1' ? 'Ok' : 'Ng'; そして最後に三項演算子が評価され、文字列の 結果:1 は、true と判断されるので前に書いた式が、echo される。 echo 'Ok'; >'Ok' 一見「何が起こったんだ!」と慌ててしまったけれども、しごく仕様通りの動きだったことが分かる。前の文字列と参考演算子の条件の結果が結合されてしまっていたんですね... 蛇足だけれど、今回のケースでは、以下のように三項演算子の部分を括弧で囲んでしまえばOK。 これで、最初に三項演算子が評価されるので、期待通りに出力されます。 $hoge = 3; echo '結果:'. (($hoge == 3) ? 'Ok' : 'Ng'); >'結果:Ok'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelを使ってチームで開発する前に決めておくべきコーディングルール

はじめに 現在Laravel × Vueを用いてサービス開発をしており、チームとしては実装担当4名でプロジェクトを進めています。開発を進めていく中で、初めのうちに決めておけばよかった!ということがあったので当記事で共有しておきます。 コーディングルールを決めておけば、プログラムの統一性が増し可読性の向上、バグの減少、命名にかける時間の削減に繋がりますので決めておくことをおすすめします。 決めておいたほうがいいこと メソッドや変数の命名規則 各ファイルの役割 ディレクトリ構成 リレーションの命名規則 routeの記述の仕方 コントローラの使い方 リソースコントローラでモデル結合ルート(ルートモデルバインディング)を使うかどうか メソッドや変数の命名規則 命名規則において以下の形式がよく用いられます。 命名規則 書き方の例 キャメルケース userProfile スネークケース user_profile パスカルケース(アッパーキャメル) UserProfile ケバブケース user-profile これはLaravelに限らずどの言語にも言えることですが、クラス名やメソッド名、変数名にそれぞれどの命名規則で記述するか、まず決めておいたほうがいいでしょう。 ちなみに今のプロジェクトではクラス名はパスカルケース、メソッドはキャメルケース、変数名はスネークケースで定義すると決めています(ただし、一部例外あり)。 中にはローカル変数に_(アンダースコア)をつけるというルールをつける場合もあります。 各ファイルの役割 LaravelはMVCのフレームワークのためContollerとModelそして、viewファイルが存在します。 基本的にこの3種類のファイルだけでもアプリケーションは作れますが、これだけだとContollerが肥大して可読性の低いfatControllerが出来上がってしまいます。より責務を分けるためにLaravelではいくつかのファイルが用意されています。 以下にいくつか紹介します。 ファイル種類 役割 Requestクラス リクエストパラメータ関連の処理をまとめる。バリデーションもここで行う MiddleWare Controllerに行く前に行いたい処理をする。IP制限や認証制限などを行える Resourceクラス APIなどでレスポンスパラメータを記述できる また、ビジネスロジックを記述するServiceクラスやUseCaseクラスを定義するパターンもよくあります。 いずれもControllerに書くこともできますがファイルを分けることによって、可読性が増し、また使い回ししやすく開発スピードの向上にもなります。 その際問題となるのがどのファイルにどこまで記述するのかです。 各ファイルをどのようにして使っていくのか、大枠でいいので軽くルール決めしておいたほうがいいでしょう。 あとは開発を進めながらチームでルールを追加修正していく必要が出てきます。 ディレクトリ構成 プロジェクト全体で使うファイル群を格納するディレクトリを決めます。 どのような構成にするかはプロジェクトによって異なってきますが、我々のプロジェクトでは以下の構成で作成しています。 src └ app ├ Http ├ Controllers ├ Api ├ Auth └ Web ├ MiddleWare ├ Requests └ Resources ├ Model ├ User ├ User.php └ UserProfile.php ├ Product ├ UserProduct.php └ UserProductItem.php └ ... ├ Services ├ UserServices.php └ ProductServices.php └ Util └ Price.php ControllerはApiとWebがあるのでそれぞれ分けています。 Modelは分けるか迷いましたが、Modelの数が増えてきたときにわかりにくくなりそうだったので今回は関心事で分けてみました。 ビジネスロジックは基本的にServiceに切り分けServicesディレクトリにまとめています。 また、プロジェクト全体で汎用的に使えるクラスをまとめるためにUtilディレクトリを用意しています。定数管理のクラスをメインにおいてます。 リレーションの命名規則 私達はここで揉めました。皆さんはリレーションをModelに定義するとき命名規則はどうしていますか? User.php // スネークケース public function user_profile() { return $this->hasOne(UserProfile::class); } // パスカルケース public function UserProfile() { return $this->hasOne(UserProfile::class); } // キャメルケース public function userProfile() { return $this->hasOne(UserProfile::class); } // リレーション使用時 public function userProfile() { $user = User::find(1); // スネークケース $address = $user->user_profile->address; // パスカルケース $address = $user->UserProfile->address; // キャメルケース $address = $user->userProfile->address; } スネークケースはカラム名と同じ命名規則でアローでつなげても自然です。ですがカラムとリレーションが混合してリレーションであるとすぐに判断できないかもしれません。 パスカルケースの場合は明確にリレーションだと理解できそうです。 もし他のメソッドがキャメルケースで統一している場合はリレーションの定義もキャメルにしておけば命名規則を合わせることができます。 リレーションはLaravelの主要な機能なので、決めておいたほうがいいでしょう。 routeの記述の仕方 ルーティングを定義するweb.phpとapi.phpにも決めておいたほうがいいことがいろいろあります。 まずはグループ化です。各ルーティングをどのようにまとめるのか(もしくはまとめないか)は決めておいたほうがいいでしょう。基本的に関心事でまとめて置くと探しやすくなります。 以下はUserとAdminで分けた例です。 api.php Route::prefix('user')->name('user.')->group(['middleware' => ['auth.api']], function () { // user関連の各ルーティングをまとめて記述 }); Route::prefix('admin')->name('admin.')->group(['middleware' => ['admin.auth.api']], function () { // admin関連の各ルーティングをまとめて記述 }); 次にControllerの記載方法です。Laravel8からはuseを使って記述できるようになり大変便利になりました。 その際、useを使うか、namespaceをフルで書くかです。 Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); Route::get('/home', [HomeController::class, 'index'])->name('home'); 個人的にはuseしたほうが見やすいのでuseしています。 もしUserとAdminで同じController名になってもuseでasを使えば別名で定義することができます。 コントローラの種類 コントローラの種類には「独自メソッド定義コントローラ」「リソースコントローラ」「シングルアクションコントローラ」があります。 make:controllerする際に何もオプションを付けなければ単なる「独自メソッド定義コントローラ」になります。 CRUD処理を記述するのであれば「リソースコントローラ」を使います。 さらに、アクションを一つだけ含むコントローラを定義したい場合「シングルアクションコントローラ」を使います。 どれを使用するかはそのコントローラで何をするかにもよりますが、CRUD処理のコントローラは「リソースコントローラ」を使おう!、あまりアクション数が増えると探すのが大変だから基本的には「シングルアクションコントローラ」にしよう!など、プロジェクトでルールを決めておくと統一性が増すでしょう。 リソースコントローラでモデル結合ルート(ルートモデルバインディング)を使うかどうか 普通にmake:controller UserProfileController -rすると以下のようなファイルが生成されます。 <?php namespace App\Http\Controllers; use Illuminate\Http\Request; class UserProfileController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { // } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { // } } 注目すべきはshowやeditアクションの引数が$idになっている点です。 ここにはルーティングで指定したパラメータが入ってきます。 基本的には$idはModelのid値が入ってきますが、make時にModelを指定すれば直接Modelを注入した形でも生成できます。 php artisan make:controller UserProfileController --model=UserProfileすると以下のようなファイルが生成されます。 <?php namespace App\Http\Controllers; use App\Models\UserProfile; use Illuminate\Http\Request; class UserProfileController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param \App\Models\UserProfile $userProfile * @return \Illuminate\Http\Response */ public function show(UserProfile $userProfile) { // } /** * Show the form for editing the specified resource. * * @param \App\Models\UserProfile $userProfile * @return \Illuminate\Http\Response */ public function edit(UserProfile $userProfile) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Models\UserProfile $userProfile * @return \Illuminate\Http\Response */ public function update(Request $request, UserProfile $userProfile) { // } /** * Remove the specified resource from storage. * * @param \App\Models\UserProfile $userProfile * @return \Illuminate\Http\Response */ public function destroy(UserProfile $userProfile) { // } } $idではなくモデルのインスタンスがはいっているのがわかるでしょう。 こちらもチームで$idにするかモデル結合ルートを使うか統一しておいたほうがいいでしょう。 私は$idにする理由はないと思っているので基本的にモデル結合ルートにしています。 まとめ チームで開発する際はなるべくコードを統一できるように工夫していく必要があります。 今回はLaravelプロジェクトにおいて議論した内容を記載してみました。 追加があれば随時追記していこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト指向プログラミング入門 for PHP programmers (2)〜オブジェクト編

この記事について この記事はオブジェクト指向プログラミング入門 for PHP programmers (1)〜前提と概念編の続編です。続編といいつつ、間がかなり空いてしまったのと、続編執筆の動機が突発的だったせいであまり前後の脈略がないかもしれませんが、その点ご容赦ください。 オブジェクト指向プログラミングとはなにか、みたいな記事は世の中に溢れていて、いまさら自分が書いてもなぁ、という気持ちでいたため、続編を書かずにいたんですが、最近以下の記事を読んで、思うところがあったので続きを書くことにしました。 使いこなすという点においては、オブジェクト指向のよくある説明の文脈と実装が綺麗に合致しないということもありそうだと感じています。例えば、よく目にする機会があるであろう車や鯛焼きの例えが、実際の設計・実装と乖離してはいないでしょうか。イメージを掴む分には良いのかもしれませんが、鯛焼きの金型と鯛焼きの話をされたとて、いざ目の前の実装にどう役立てて良いのかよくわかりません。 たしかに、オブジェクト指向プログラミングを解説するときに、動物やら鯛焼きやらの例は見たことがあり、現実のプロジェクトで使うような例を用いた解説が少ないように思ったので、では、「実際の設計・実装」とあまり乖離してない例で解説してみよう、と思ったのがきっかけです。 本記事では、そうした例を示しながら、オブジェクト指向プログラミングにおけるオブジェクトの作り方と使い方および利点について示せればと思います。まずはオブジェクトにフォーカスしているため、継承やインタフェースなどは続編で書く予定です。 はじめに PHP におけるオブジェクト指向プログラミングの特徴 PHP はマルチパラダイムプログラミング言語(手続き型、オブジェクト指向、関数型いずれでも記述可能な言語)なので、純粋なオブジェクト指向言語ではありません。したがって、オブジェクト指向的に書かなくてもアプリケーションをつくることは可能です。しかし、近年は純粋なオブジェクト指向言語であるかのように書くことが一般的な潮流なので、現代の PHP プログラミングでは、オブジェクト指向を避けて通ることはできません。 純粋なオブジェクト指向ではないので、数値や文字列といったプリミティブ型(言語が備えているもともとのデータ型)はクラスではありません。また、標準関数も基本的にはオブジェクト指向でない形で提供されています。そのあたりが Java や Ruby などの純粋なオブジェクト指向言語とは異なっていて、PHP でオブジェクト指向プログラミングをする上でのひとつの障害になっているかもしれませんが、そこはもう付き合っていくしかないと割り切っていくしかないでしょう。 オブジェクトとはなにか PHP でのオブジェクト指向プログラミングにおけるオブジェクトとは、「クラスのインスタンス」のことです。 クラスは、以下の構文で定義します。 class Name { } 実際に使う際はプロパティやらメソッドやらが入ってきますが、定義自体はこれだけで OK です。クラス名のみ、自分で考えて決める必要があります。定義なので、これだけ書いて実行してもなにも起こりません。 インスタンスはクラスを new して返ってきた値です。ためしに、以下のように書いて実行してみてください。 class Name { } // $name がインスタンス、あるいはインスタンス変数と呼ばれる $name = new Name(); var_dump($name); // 以下が出力される // object(Name)#1 (0) { // } 中身のないクラスなので、これ以上説明できることはありませんが、クラスとはなにか、という定義はこれで十分です。 環境 PHP: 8.0.10 まだ PHP 8 に馴染みのないかたもいると思うので、8 から導入された構文や機能には注釈を入れます。 目次 以下のカテゴリでひとつずつ「実際の設計・実装」とあまり乖離してない例を紹介します。 数値 文字列 連想配列 ロジック 1. 数値 前述のとおり PHP における数値はオブジェクトではないですが、独自のクラスをつくることでオブジェクトとして表現することができます。 数値なんか普通の int/float でよくね?というご意見はごもっともです。普段は私もあまりクラス化はしてないです。ただ、特定の局面で役に立つことがあるので、そうした(後述します)局面に出くわしたら思い出してください。 たとえば、年齢を扱う処理を考えてみましょう。例として、お酒の販売などで、ユーザーが商品を購入可能な年齢かどうか確認したいケースを想定します。 1.1. 例)値の比較 まずは、非オブジェクト指向のコードとオブジェクト指向のコードを比較してみます。 非オブジェクト指向バージョン /** * @param int $age * @return bool */ function isAdult(int $age): bool { return $age >= 20; } $age = 20; echo "$age is adult? ", isAdult($age) ? 'yes' : 'no', PHP_EOL; // 20 is adult? yes オブジェクト指向バージョン class Age { public function __construct(public int $value) {} // PHP 8 未満であれば次の構文 /* public int $value; public function __construct(int $value) { $this->value = $value; } */ public function isAdult(): bool { return $this->value >= 20; } public function __toString(): string { return "{$this->value}"; } } $age = new Age(20); echo "$age is adult? ", $age->isAdult() ? 'yes' : 'no', PHP_EOL; // 20 is adult? yes どちらも大差はないですが、出力の際に int 型は直接埋め込めるのに対し、Age 型の場合は __toString() メソッドを持たせないとそれができない点だけが大きく異なります。 1.2. ドメイン制約をつける 年齢は確実に正の整数ですが、int 型の場合は負の整数も代入可能です。一般的なウェブアプリケーションフレームワークにはバリデーションの仕組みが備わっており、ユーザーからの入力を、定めたルールにしたがって弾くことができます。それでも、バリデーションの記述を間違えた場合にすり抜けてくる可能性はあるので、より確実な制約を課す必要がある局面があります(今回のケースでは 20 歳以下、というルールなので、仮に負の整数がきてもエラーにはなりませんが、対象によっては実行時エラーになるケースがあります。たとえば、取りうる値が 1 から 5 までであるなんらかのスコア的なものであれば、0 以下 や 6 以上の値を弾かなければならないケースはありえます。 非オブジェクト指向バージョン function isAdult(int $age): bool { if ($age < 0) { throw new InvalidArgumentException('年齢は0未満にはできません。'); } return $age >= 20; } オブジェクト指向バージョン public function __construct(public int $value) { if ($value < 0) { throw new InvalidArgumentException('年齢は0未満にはできません。'); } } 両者の違いは、非オブジェクト指向バージョンでは年齢を扱う関数内でチェックしているのに対し、オブジェクト指向バージョンではインスタンスの生成時にチェックしている点です。もちろん、非オブジェクト指向バージョンでも、値のチェックを isAdult 関数の外に出し、値の代入直後にチェックすることは可能です。 function validateAge(int $age): void { if ($value < 0) { throw new InvalidArgumentException('年齢は0未満にはできません。'); } } $age = -1; validateAge($age); // エラー echo "$age is adult? ", isAdult($age) ? 'yes' : 'no', PHP_EOL; しかし、頻繁に値が生成(代入)されるようなクラスだった場合、生成される箇所すべて値のチェックを行うよりは、生成の際に一緒にやるほうが、非オブジェクト指向バージョンには記述漏れのリスクがある分オブジェクト指向バージョンのほうが安全です。 というわけで、初期化時にチェックするようにすると、オブジェクト指向バージョンにかなり近くなります。 function initAge(int $age): int { if ($value < 0) { throw new InvalidArgumentException('年齢は0未満にはできません。'); } return $value } $age = initAge(20); echo "$age is adult? ", isAdult($age) ? 'yes' : 'no', PHP_EOL; ここまでやるならオブジェクト指向でよくね?ってなる気がします。 1.3. 生成を制限する ECサイトの場合は年齢を直接持つのではなく、生年月日を持つのが一般的でしょう。そうした場合、年齢は生年月日から導出されなければなりません。isAdult 関数にて、生年月日をもとに年齢を計算した上で成人かどうかを判断するように変更します。 非オブジェクト指向バージョン function calculateAgeFromBirthday(DateTimeImmutable $birthday): int { $today = new DateTimeImmutable(); return (int)(($today->format('Ymd') - $birthday->format('Ymd')) / 10000); } function isAdult(int $age): bool { if ($age < 0) { throw new InvalidArgumentException('年齢は0未満にはできません。'); } return >= 20; } $age = calculateAgeFromBirthday(new DateTimeImmutable('2001-01-01')); echo "$age is adult? ", isAdult($age) ? 'yes' : 'no', PHP_EOL; // 20 is adult? yes オブジェクト指向バージョン class Age { private function __construct(public int $value) { if ($value < 0) { throw new InvalidArgumentException('年齢は0未満にはできません。'); } } public static function createFromBirthday(DateTimeImmutable $birthday): self { $today = new DateTimeImmutable(); return new self((int)(($today->format('Ymd') - $birthday->format('Ymd')) / 10000)); } public function isAdult(): bool { return $this->value >= 20; } public function __toString(): string { return "{$this->value}"; } } $age = Age::createFromBirthday(new DateTimeImmutable('2001-01-01')); echo "$age is adult? ", $age->isAdult() ? 'yes' : 'no', PHP_EOL; // 20 is adult? yes この結果、オブジェクト指向バージョンは、生年月日がなければ Age を外部から生成することができなくなりました。さらに、生年月日もユーザー定義型にして、生年月日には未来の日付を受け付けない、などの制約を課すことで、より不正な値の侵入を防げるようになりますが、繰り返しになるのでソースコードは省略します。 クラス化するとうれしいケース例 頻繁に代入されうる値について範囲などの制約を強制したいケース 値の生成ロジックを制限したいケース 2. 文字列 続いて文字列の例です。私自身は、数値よりオブジェクト化する頻度が高いデータ型です。おもに決まったフォーマットや変換がともなうデータである場合にクラス化します。 2.1. 例)電話番号の国際化 例として、電話番号を扱うシステムをつくっていて、システムから海外に電話がかけられる仕様があるとします。Twilio みたいなサービスを使っていて、内部ではローカルの電話番号を保持しているが、通話サービスを利用する際には変換してから渡したい、みたいなケースです。日本以外へ電話をかける場合、E.164形式という、国ごとのコードとローカルの番号を組み合わせた形式に変換する必要があります。 非オブジェクト指向バージョン function internationalizePhoneNumber(string $localNumber, string $countryCode): string { return '+' . $countryCode . '-' . substr($localNumber, 1); } $phoneNumber = '090-1234-5678'; $internationalPhoneNumber = internationalizePhoneNumber($phoneNumber, '81'); echo $phoneNumber, ' -> ', $internationalPhoneNumber, PHP_EOL; // 090-1234-5678 -> +81-90-1234-5678 オブジェクト指向バージョン class PhoneNumber { public function __construct(private string $value) {} public function internationalize(string $countryCode): string { return '+' . $countryCode . '-' . substr($this->value, 1); } public function __toString(): string { return $this->value; } } $phoneNumber = new PhoneNumber('090-1234-5678'); $internationalPhoneNumber = $phoneNumber->internationalize('81'); echo $phoneNumber, ' -> ', $internationalPhoneNumber, PHP_EOL; // 090-1234-5678 -> +81-90-1234-5678 数値の例と同様、この時点ではどちらも似たような感じです。 ここに、ローカルの電話番号のフォーマットと国番号の制約がほしいとなったら、先ほどのようにバリデーションロジックを入れる必要がある点は同じです。 2.2. 値の変換 あるいは、ハイフンなしのフォーマットとありのフォーマットを両方受け取れるようにして、内部ではハイフンなしで保持したい、となったら、生成の際に変換してから格納することも可能になります。 非オブジェクト指向バージョン private function normalizePhoneNumber(string $value): string { return str_replace('-', '', $value); } $phoneNumber = normalizePhoneNumber('090-1234-5678'); $internationalPhoneNumber = internationalizePhoneNumber($phoneNumber, '81'); // 以下略 オブジェクト指向バージョン public function __construct(private string $value) { // フォーマットチェックが必要ならチェックしてから正規化 $this->value = $this->normalize($value); } private function normalize(string $value): string { return str_replace('-', '', $value); } 「頻繁に代入されうる値について範囲などの制約を強制したいケース」であれば、バリデーションしてるから他ではチェックしなくてもいいよね、という選択肢は取りうるわけですが、値の変換に関しては避けることはできません。 数値の例と同様、こちらも生成と変換をコンストラクタ内で同時に行うので、変換漏れを防ぐことができます。 クラス化するとうれしいケース例 値の変換を強制したいケース 3. 連想配列 数値や文字列よりもさらにクラス化のメリットを受けやすいのが連想配列です。PHP の場合、あるまとまった意味のあるデータの集合を扱うときに連想配列を使うことが多いと思いますが、ロジックが絡んでくる場合には連想配列のままよりもクラス化してしまったほうが書きやすいことが多くなる気がします。 3.1. 例)緯度経度 例として、緯度経度をセットで持つタプルのようなデータ型を考えてみます。余談ですが、一般的には「緯度」「経度」の順に記述しますが、両方とも float 型であるのと、英語の "latitude" "longitude" の表記と相まってどっちがどっちかわかりづらいデータなので(そんなことないですか?)、ミスを防ぐためにもクラス化しておきたいデータの一例です。 非オブジェクト指向バージョン $coordinate = [ 'lat' => 35.67747385964994, 'lng' => 139.71717545047113, ]; echo "{$coordinate['lat']},{$coordinate['lng']}", PHP_EOL; オブジェクト指向バージョン class Coordinate { public function __construct(private float $lat, private float $lng) {} public function __toString(): string { return "{$this->lat},{$this->lng}"; } } $coordinate = new Coordinate(lat: 35.67747385964994, lng: 139.71717545047113); // 引数の名前を指定して渡せる named arguments は 8 からの新構文です // 8 未満では以下のように書いてください // $coordinate = new Coordinate(35.67747385964994, 139.71717545047113); echo $coordinate, PHP_EOL; 前述のとおり、値の範囲チェックのロジックを書くことで、緯度経度を誤って逆にするのを防ぐことができます(コードは省略します)。 文字列表現は、たとえば GoogleMap API にパラメータとして渡す際に必ず決まったフォーマットになるよう強制できますので、これも一種の変換処理です。 3.2. 引数の型を制限する もう一つ、これは比較的小さいメリットですが、型を強制できる、というものがあります。二点間の距離を算出する関数が必要になったケースを考えてみます。 非オブジェクト指向バージョン /** * @param array{lat: float, lng: float} $from * @param array{lat: float, lng: float} $to * @return int */ function distance(array $from, array $to): int { // $from, $to ともに、lat, lng を持っているかどうか保証されていない // 距離計算(省略) } いちおうコメントに記載することでパラメータで渡すべき連想配列の中身がどういうものであるかを明示することはできますが(ただし、上の記法は phpDoc 標準ではない(はず)ので、IDE やエディタによってはうまく解釈されないかもしれません)、どちらのパラメータも中身が不明なので、両方とも緯度経度を持っているかどうかチェックしてやる必要があるかもしれません。緯度経度を用いた関数が複数になると、その都度チェックすることになるので、コストがかかってしまいます。 オブジェクト指向バージョン /** * @param Coordinate $from * @param Coordinate $to * @return int */ function distance(Coordinate $from, Coordinate $to): int { // $from, $to ともに、lat, lng を持っているかどうか保証されている // 距離計算(省略) } いっぽう、オブジェクト指向バージョンはユーザー定義型が指定してあるので、二点の緯度経度が確実に渡ってくる(Coordinate クラスでチェックしていれば)ことが事前にわかっています。したがって、緯度経度を用いた関数が増えても、毎回引数のチェックをする必要がなくなります。 今回の例ではキー(プロパティ)が2つなので、それほど負担ではないかもしれませんが、もっと増えてくると連想配列だときつく感じるようになると思います(たとえば、以前つくったサービスでは、ユーザーごとの様々なオプションの値に基づいてそのユーザーが割引対象になるかどうか判定する、みたいなロジックがあって、それらのオプションをまとめてクラス化し、割引計算では、そのオプションクラスのインスタンスを渡すようにしました)。 クラス化するとうれしいケース例 関数に引数として渡す際にユーザー定義型を強制することでデータの中身が想定した形式になっていることを保証しておきたいケース 4. ロジック これまでの例はおもに PHP が提供するプリミティブ型をラップして、制約を課すためにオブジェクトを使う、というユースケースでしたが、ロジックをオブジェクトにすることも可能です。ロジックとはつまり式、文、関数です。変化するデータを持たず、判定や変換のような処理のみを受け持つシンプルなクラスになることが多いです。 4.1. 例)年齢認証が必要な商品が含まれているかどうか ECサイトでカートに含まれている商品リストの中に年齢認証が必要な商品が含まれているかどうかをチェックし、あれば確認メッセージを出す、というロジックを考えてみます。ウェブアプリケーションであれば、メッセージはフロントエンドで出るでしょうから以下のような使い方はありえませんが、あるデータがなんらかの条件を満たしていたらあるアクションを起こす、というケースは頻繁にありうるので、そうしたシチュエーションであれば適用できると思います。 年齢認証が必要かどうかのキー(プロパティ)はオプショナルで、キーが存在しなければ不要、というルールになっています。 非オブジェクト指向バージョン define('AGE_THRESHOLD', 20); function isRequiredToVerifyAge(array $items): bool { foreach ($items as $item) { if (isset($item['is_required_age_verification']) && $item['is_required_age_verification']) { return true; } } return false; } $items = [ ['name' => 'お菓子'], ['name' => 'お酒', 'is_required_age_verification' => true], ]; if (isRequiredToVerifyAge($items)) { echo AGE_THRESHOLD . '歳以上ですか?', PHP_EOL; } オブジェクト指向バージョン class Item { public function __construct( public string $name, public bool $isRequiredAgeVerification = false, ) {} } class VerifyAge { private const THRESHOLD = 20; public function shouldVerify(array $items): bool { foreach ($items as $item) { if ($item->isRequiredAgeVerification) { return true; } } return false; } public function prompt(): string { return self::THRESHOLD . '歳以上ですか?'; } } $items = [ new Item(name: 'お菓子'), new Item(name: 'お酒', isRequiredAgeVerification: true), ]; $verification = new VerifyAge(); if ($verification->shouldVerify($items)) { echo $verification->prompt(), PHP_EOL; } 両者の大きく異なる点は、閾値(この場合は成人かどうかを判断するための閾値)がグローバルになっているかクラスの中に隠蔽されているか、です。小さな差異としては、年齢認証が必要かどうかのプロパティにデフォルト値が与えられるかどうか、くらいでしょうか。 さて、お酒を購入できるのは20歳以上ですが、近い将来(2022年4月)成人年齢が18歳に引き下げられ、これまで保護者の同意がなければ買えなかったもの(たとえば携帯電話の契約)が買えるようになる年齢が下がります。この結果、年齢認証の閾値が18歳と20歳のパターンが発生することになります。 「年齢認証」というアクションに対してロジックが複数存在するので、商品の種類に応じてロジックを差し替える必要が出てくるでしょう。 この辺りは継承も絡んでくるので以下次号とさせてください。いちおうクラス化するとうれしいケース例には挙げておきます。 クラス化するとうれしいケース例 ひとつのアクション対してロジックが複数存在するケース おわりに いかがでしたでしょうか?守秘義務などの理由により「実際の設計・実装」はなかなか表に出ることはないので、あくまでも「実際の設計・実装」とあまり乖離してない例になりますが、非オブジェクト指向とオブジェクト指向との違い、メリットを感じていただけたでしょうか?少しでもオブジェクト指向に対する理解を深める手助けになれば幸いです。 最後にこれまでバラバラに出してきたメリットをまとめます。 クラス化するとうれしいケース例 頻繁に代入されうる値について範囲などの制約を強制したいケース 値の生成ロジックを制限したいケース 値の変換を強制したいケース 関数に引数として渡す際にユーザー定義型を強制することでデータの中身が想定した形式になっていることを保証しておきたいケース ひとつのアクション対してロジックが複数存在するケース 他にもこんなメリットがあるんじゃない?、という方がいたらぜひコメント欄にて教えてください
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MAMPでtimezoneの変更

デフォルトでは日本の時間で表示されないので、日本の時間で表示する変更方法を書いてみました。 大きくわけて 1.MAMPでPHP versionを確認。 2.timezoneを書き換える。 3.MAMPを再起動してtimezoneが変更されているか確認。 すれば変更できます。 1.MAMPでPHP versionを確認。 MAMPを起動する(stratを押す)。 WebStartをおして、phpinfoをクリック。 PHP versionが書いてるので確認する。 2.timezoneを書き換える。 Applications>MAMP>bin>php>php7.4.16>conf>php.ini php.iniのtimezoneを変更する。(php7.4.16は先ほど確認したversionのものを選択する) デフォルトは"Europe/Berlin"になっている。 今回は日本にしたいので"Asia/Tokyo"と書き換え保存する。 (date.timezoneの前についている[;]を削除する。削除しないと変更されない。) 3.MAMPを再起動してtimezoneが変更されているか確認。 MAMPを一度Stopして、またStratする。Webstratをおしてphpinfoをみる。timezoneがAsia/TokyoになっていたらOK。 これで日本の時間に変更されると思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【簡単3 steps】Vue.jsサンプルをLaravelアプリに実装する方法

今回の記事は下記の方におすすめです! Laravelの基礎はOK Vue.jsの基礎はOK でもLaravelでVue.jsサンプルを使う方法が不明 自分でゴリゴリコードを書くのもありですが、最初のうちは「Vue.js」のサンプルをLaravelのサイトに埋め込むところから始めるのはありかもしれません。 今回は、「Vue.js」のサンプルが多数紹介されている海外のサイトを紹介しつつ、サンプルを実際の自分のプロジェクトに埋め込む方法について共有いたします! 今回の記事が参考になれば幸いです なお、間違いやご指摘ありましたら、コメント下さると幸いです。 実行環境 PHP 7.4 Laravel 6.2 Vue.js 2.5 ゴール Laravelのアプリ内でvue.jsのサンプルを実装する ちゅうい!! なお、Vue.jsがコンパイルできる環境が構築されている前提で話を進めていきます。 まだの方は、下記の記事の1)~4)を完了させ、「npm run dev」でコンパイルできる状態にしてください。 コンパイルまでの参考記事:【初心者向】Laravel 6系でVue.jsを使用する方法 laravel/uiパッケージをインストール 「composer require laravel/ui:^1.0 --dev」 vue.jsのファイル生成 「php artisan ui vue」 依存パッケージのインストール 「npm install」 js/sassファイルのコンパイル実行 「npm run dev」 ココまで↑は最低限実行しておいてください。詳細は上記記事にて。 Vue.jsサンプルサイト ちなみに下記のサイトではVue.jsを使ったサンプルがまとめられています 「スクロール」や「ポップアップ」など便利な部品が多数まとめられているので、是非一度目を通してみてください。 サンプルまとめサイト:Vue.js Examples ↑こんな感じで、サンプルによっては、Gif動画形式で、挙動を確認できるので、イメージしやすいかと思います。 今回埋め込むサンプル「scroll」 今回は、「スクロール」をLaravelサイトに埋め込んでいこうと思います。画面をスクロールすると、画面上部緑の進捗バーが進捗します。 今回Laravelで使用するサンプル:scroll progress bar ↓緑のバーが進捗 手順 Vue.jsサンプルをインストール Laravelと紐付ける インストールしたVue.jsサンプルをコンパイル 1)Vue.jsサンプルをインストール npm i vue-scroll-progress --save まず、パッケージをインストールします。上記コマンドで「node_modules」ディレクトリ以下に「vue-scroll-progress」がインストールされるかと思います。 この時点でエラーが出た方は、ご自分の開発環境のバージョンとサンプルのバージョンが競合している可能性があるので、競合しなさそうな(エラーにならない)ものをサンプルサイトから探してみてください。 2)Laravelと紐付ける ただインストールしただけでは使用できません。 Laravelのアプリ側からは、見えていないからですね。 そこでLaravelのアプリ側が認識してくれる場所に 「この機能を使いますよー」宣言をします。 具体的に言うと、「resources/js/app.js」ファイルへの追記ですね。 import VueScrollProgress from 'vue-scroll-progress'; Vue.use(VueScrollProgress); 「resources/js/app.js」ファイルに上記の記述を追加してください。 resources/js/app.js // (省略) require('./bootstrap'); window.Vue = require('vue'); // 以下2行追加 import VueScrollProgress from 'vue-scroll-progress'; Vue.use(VueScrollProgress); // (省略) Vue.component('example-component', require('./components/ExampleComponent.vue').default); 「import」の意味がわからない方はこちらの記事を参考にしてみてください。 参考:jsのimportとrequireの違い 参考:JavaScriptのimportとは? 機能ごとに分割したjsファイルを、読み込んで利用する=インポートするイメージです。 3)インストールしたVue.jsサンプルをコンパイル npm run dev 上記コマンドでコンパイルを実行すると... 進捗バーが表示されました! (ちょっとわかりにくいけど、一番上の緑のボーダーですね) 応用事例 これを応用すれば、ユーザーが入力するフォーム画面で 進捗を表示することで、途中での離脱を防ぐ効果なんかも得られそうですね 今回は、「app.js」ファイルに記述しましたが、読み込みたい部分で 部品として使うこともできるようですよ。 <template> <VueScrollProgress></VueScrollProgress> </template> 今回の記事は以上になります お役に立てたなら幸いです!! 参考 たった3ステップ!Laravelでnpmパッケージを使う方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP よく使うString関数まとめ

本記事の内容 本記事では、String関数、マルチバイト文字列関数である、 explode (文字列を指定した文字列により分割する) substr (文字列の一部分を返す) nl2br (改行文字の前に HTML の改行タグを挿入する) str_replace (検索文字列に一致したすべての文字列を置換する) strpos (文字列内の部分文字列が最初に現れる場所を見つける, 文字列が存在するかの確認) htmlspecialchars (特殊文字を HTML エンティティに変換する) の使い方をざっくりまとめています。 さらに詳しく知りたいという方は、記事最後に公式ドキュメントのページを一覧で記載しています。 (順次追加です) 一つの関数あたり、 関数の詳細(公式リファレンスに沿った具体的なもの) ざっくりした説明 の二部構成です。  使い方に関しては、ざっくりした説明だけを見ていただければ理解できる程の情報量を簡潔にまとめています。 (誤解を招く表現等ありましたらコメントでお願いします) explode関数 explode関数 explode 詳細 対応バージョン PHP 4, PHP 5, PHP 7, PHP 8 説明 文字列を区切り文字によって分割し、配列として保存する explode(string $separator, string $string, int $limit = PHP_INT_MAX): array separator 区切り文字列 string 入力文字列 limit limit に正の値が指定された場合、返される配列には 最大 limit の要素が含まれ、その最後の要素には string の残りの部分が全て含まれます。 もし limit パラメータが負の場合、 最後の -limit 個の要素を除く全ての構成要素が返されます。 limit パラメータがゼロの場合は、1 を指定したものとみなされます。 ※基本的に使用する場合limitの指定は必要ではありません explode 使い方 $data = "apple,banana,orange"; $output = explode(',', $data); // 出力用 var_dump($output); 出力 array(3) { [0]=> string(5) "apple" [1]=> string(6) "banana" [2]=> string(6) "orange" } 区切り文字を「,」に指定したため、文字列の「,」の部分で三分割されています。 非常に便利な関数です。 substr関数 substr関数 substr 詳細 対応バージョン PHP 4, PHP 5, PHP 7, PHP 8 説明 文字列の一部分を切り取る substr(string $string, int $offset, ?int $length = null): string string 入力文字列。 offset offset が正の場合、返される文字列は、 string の 0 から数えて offset番目から始まる文字列となります。 例えば、文字列'abcdef'において位置 0にある文字は、'a'であり、 位置2には'c'があります。 offset が負の場合、返される文字列は、 stringの後ろから数えて offset番目から始まる文字列となります。 string の長さが offset 文字より短い場合は false が返されます。 例1 負の offset の使用 $rest = substr("abcdef", -1); // "f" を返す $rest = substr("abcdef", -2); // "ef" を返す $rest = substr("abcdef", -3, 1); // "d" を返す length length が指定され、かつ正である場合、 返される文字列は offset (string の長さに依存します) から数えてlength文字数分となります。 length が指定され、かつ負である場合、 string の終端からその文字数分の文字が省略されます (offset が負の場合は、 開始位置を算出したあとで)。 もし offset が切り出し位置を超える場合、 false が返されます。 length が指定され、かつ 0、null、もしくは false であれば、空の文字が返されます。 length を省略した場合は、 offset の位置から文字列の最後までの部分文字列を返します。 substr 使い方 $data = "aiueo"; // 1番目から1バイト抜き取る $output = substr($data,0,1); // 出力用 var_dump($output); // 出力 string(1) "a" ここで注目してほしいところが、「バイト」単位であることです。 その為、日本語などマルチバイト文字では適切に処理ができません。 マルチバイト文字を少しでも使う可能性がある場合、mb_substr関数を使用してください。 $data = "あいうえお"; // 2番目から1文字抜き取る $output = mb_substr($data,1,1); // 出力用 var_dump($output); 出力 string(3) "い" 「バイト」ではなく「文字」で設定が可能です。 また、二番目の引数は、0から数え始めます。例えば10文字目であれば9となります。 マルチバイトを扱う場合は、mb_substrを使用する nl2br関数 nl2br関数 nl2br 詳細 対応バージョン PHP 4, PHP 5, PHP 7, PHP 8 説明 改行文字の前に HTML の改行タグを挿入する nl2br(string $string, bool $use_xhtml = true): string string 入力文字列。 use_xhtml XHTML 準拠の改行を使うか否か。 (デフォルト:true) nl2br 使い方 $data = "aiueo\nkakikukeko"; // \nの前に<br />を挿入 $output = nl2br($data); // 出力用 var_dump($output); 出力 string(22) "aiueo<br />\nkakikukeko" 改行コードをそのままHTMLに変換する時に便利です。 また、 $output = nl2br($data,false); とすることで、<br />ではなく、<br>と出力することができます。 str_replace関数 str_replace関数 str_replace 詳細 対応バージョン PHP 4, PHP 5, PHP 7, PHP 8 説明 検索文字列に一致したすべての文字列を置換する str_replace( array|string $search, array|string $replace, string|array $subject, int &$count = null ): string|array search 探したい値。needle (針) と呼ばれることもあります。 配列を使えば、複数の値を指定することもできます。 replace 見つかった search を置き換える値。 配列を使えば、複数の値を指定することもできます。 subject 検索・置換の対象となる文字列あるいは配列。 haystack (干し草の山) と呼ばれることもあります。 subject が配列の場合、 subject の各エントリについて検索と置換が行われ、 返り値は同様に配列となります。 count 指定した場合は、マッチして置換が行われた箇所の個数がここに格納されます。 str_replace 使い方 文字列バージョン 一つを置き換える場合に使用します。 $data = "aiueokakikukeko"; // kaをsaに置き換える $output = str_replace('ka','sa',$data); // 出力用 var_dump($output); 出力 string(15) "aiueosakikukeko" 配列バージョン str_replaceは配列で指定することで、複数を同時に置き換えることも可能です。 $data = "aiueokakikukeko"; // kaをsaに、kiをsiに置き換える $output = str_replace(['ka','ki'],['sa','si'],$data); // 出力用 var_dump($output); 出力 string(15) "aiueosasikukeko" 配列の順番通りに置き換えてくれます。 また、何個置き換えたのかを知りたいときは、 $output = str_replace(['ka','ki'],['sa','si'],$data,$count); と四番目の引数に新しい変数名を指定することで、この例の場合は$countにint型で挿入してくれます。 例えば先ほどの例であれば1、何も置き換えをしなければ0となります。 strpos関数 strpos関数 strpos 詳細 対応バージョン PHP 4, PHP 5, PHP 7, PHP 8 説明 文字列内の部分文字列が最初に現れる場所を見つける strpos(string $haystack, string $needle, int $offset = 0): int|false haystack 検索を行う文字列。 needle PHP 8.0.0 より前のバージョンでは、needle が文字列でない場合、 数値に変換され、文字の通常の値として扱われていました。 この振る舞いは PHP 7.3.0 以降では推奨されないので、 この機能を使用しないことを強く推奨します。 意図した動作に依存する場合、 needle を string に明示的にキャストするか、 明示的に chr() 関数を呼び出すべきでしょう。 offset 指定すると、文字列内での検索開始位置がその位置になります。 負の数を指定すると、文字列の末尾からこの数だけ戻った場所から検索を開始します。 strpos 使い方 文字列が存在するかの確認 strposでは検索のほかに、文字列が存在するかの確認に利用できます。 $data = "aiueokakikukeko"; // $dataにkoが存在するかを確認する if(strpos($data,('ko'))!==false){ $output="存在します"; }else{ $output="存在しません"; } // 出力用 var_dump($output); 出力 string(15) "存在します" これは、検索する文字列が存在しない場合にstrposがfalseを返却することを利用して判断しています。 文字列を検索する場合 strposの標準の使い方です。 $data = "aiueokakikukeko"; // $dataにkoがどこに存在するかを確認する $output = strpos($data,'ko'); // 出力用 var_dump($output); 出力 int(13) ちなみにカウントの開始位置が0から始まる為、この結果では14番目(13+1)にkoが最初に出現したことが分かります。 0からカウントされることに注意 htmlspecialchars関数 htmlspecialchars関数 htmlspecialchars 詳細 対応バージョン PHP 4, PHP 5, PHP 7, PHP 8 説明 特殊文字を HTML エンティティに変換する htmlspecialchars( string $string, int $flags = ENT_COMPAT, ?string $encoding = null, bool $double_encode = true ): string string 変換される文字列。 flags 以下のフラグを組み合わせたビットマスクです。 クォートや無効な符号単位シーケンス、そして文書型の扱いを指定します。 デフォルトは ENT_COMPAT | ENT_HTML401 です。 利用可能な flags 定数 定数名 説明 ENT_COMPAT ダブルクオートは変換しますがシングルクオートは変換しません。 ENT_QUOTES シングルクオートとダブルクオートを共に変換します。 ENT_NOQUOTES シングルクオートとダブルクオートは共に変換されません。 ENT_IGNORE 無効な符号単位シーケンスを含む文字列を渡したときに、 空の文字列を返すのではなく無効な部分を切り捨てるようになります。 このフラグは使わないようにしましょう。セキュリティの問題が発生する可能性があります。 ENT_SUBSTITUTE 無効な符号単位シーケンスを含む文字列を渡したときに、 空の文字列を返すのではなく Unicode の置換文字に置き換えます。 UTF-8 の場合は U+FFFD、それ以外の場合は &#xFFFD; となります。 ENT_DISALLOWED 指定した文書型において無効な符号位置を、Unicode の代替文字である U+FFFD (UTF-8) あるいは &#xFFFD; で置き換えます。 これを設定しなければ、無効な符号位置をそのまま残します。 これは、外部コンテンツを埋め込んだ XML 文書を整形式に保つために有用です。 ENT_HTML401 コードを HTML 4.01 として処理します。 ENT_XML1 コードを XML 1 として処理します。 ENT_XHTML コードを XHTML として処理します。 ENT_HTML5 コードを HTML 5 として処理します。 encoding オプションの引数。文字を変換するときに使うエンコーディングを定義します。 省略した場合の encoding のデフォルト値は、 default_charset の値を使います。 技術的にはこの引数を省略可能ですが、 default_charset の指定が入力とは違う文字セットになっている可能性もあるので、 適切な値を指定しておくことを強く推奨します。 この関数を使ううえでは ISO-8859-1 と ISO-8859-15、 UTF-8、cp866、 cp1251、cp1252 そして KOI8-R は事実上同等です。 string 自体がそのエンコーディングにおける有効な文字列である限り、 これらのエンコーディングでは htmlspecialchars() の影響が及ぶ文字がみな同じ位置にあるからです。 サポートされている文字セットの詳細は、記事下のリンクに掲載されています。 double_encode double_encode をオフにすると、PHP は既存の html エンティティをエンコードしません。 デフォルトでは、既存のエンティティも含めてすべてを変換します。 htmlspecialchars 使い方 変換対象となる文字 変換前 変換後 & (アンパサンド) &amp; " (ダブルクォート) ENT_NOQUOTES が指定されていない場合、&quot; ' (シングルクォート) &#039; (ENT_HTML401 の場合) あるいは &apos; ( ENT_XML1、ENT_XHTML、 ENT_HTML5 の場合)。ただし ENT_QUOTES が指定されている場合に限る < (小なり) &lt; > (大なり) &gt; 基本的に、対象の文字列と必要に応じてflagsを使用します。 //<span onclick="hello('click')">こんにちわ!</span> $data = '<span onclick="hello('."'click'".')">こんにちわ!</span>'; // $dataを変換 $output = htmlspecialchars($data); // 出力用 var_dump($output); 出力 string(78) "&lt;span onclick=&quot;hello('click')&quot;&gt;こんにちわ!&lt;/span&gt;" デフォルトではこのように、変換される「&,",',<,>」の中で「'」以外が変換されます。 ちなみに、「&,<,>」に関してはflagsで指定しても変換を停止することはできません。 基本的に使用するのはこの3つのflagsです。 一番最初のENT_COMPATはデフォルトなので指定する必要はありません。 定数名 説明 ENT_COMPAT ダブルクオートは変換しますがシングルクオートは変換しません。 ENT_QUOTES シングルクオートとダブルクオートを共に変換します。 ENT_NOQUOTES シングルクオートとダブルクオートは共に変換されません。 もしENT_NOQUOTESを指定すると、 出力 string(68) "&lt;span onclick="hello('click')"&gt;こんにちわ!&lt;/span&gt;" シングルクート、ダブルクオートともに変換されなくなります。 用途に合わせて使い分けましょう。 htmlspecialchars関数は、XSS(クロスサイトスクリプティング)を防ぐための非常に重要な関数です HTMLにユーザーの入力した文字列を表示する際、この関数を忘れないようにしましょう。 XSS(クロスサイトスクリプティング) - wikipedia 参考ページ 参考にした公式ドキュメントのページです。 explode - PHPマニュアル substr - PHPマニュアル nl2br - PHPマニュアル str_replace - PHPマニュアル strpos - PHPマニュアル htmlspecialchars - PHPマニュアル
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ファイルアップロードツールを作ってみた

はじめに PHPとJavaScriptを使ってブラウザ上で動作するシンプルなファイルアップロードツールを作りました。 Chromeブラウザで動作します。 ツールイメージ ツールのイメージはこんな感じになります。 ①テキストを保存する領域です。テキストエリアになっているので複数行保存出来ます。 ②上部のテキストの内容を保存するボタンです。 ③アップロードされているファイル一覧が表示される領域です。 ファイルがアップロードされている場合は、ファイル名・ダウンロードボタン・削除ボタンが表示されます。 ④ファイルアップロード領域です。ここにファイルをドラッグ&ドロップするとファイルがアップロードされます。 ファイル構成 ツールのファイル構成はこのようになっています。 fileフォルダ・・アップロードしたファイルが格納されるフォルダ Ajax.inc・・Ajax処理をまとめているJavaScriptライブラリファイル index.html・・ブラウザのページファイル※このファイルにアクセスしてツールを起動します server.php・・サーバ側の処理を行うPHPファイル text.txt・・テキストの入力欄に入力し保存された文字を保存するテキストファイル クライアント側 クライアント側のソースは以下になります。 index.html <!DOCTYPE html> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>file upload</title> <script type="text/javascript" src="Ajax.inc"></script> <script type="text/javascript"> window.onload = function(){ // テキスト読み込み sendData = createQueryParam( "flg", 1 ); ajaxNetConnect( "./server.php", sendData, "POST", 1, "text_load_complete" ); } // テキスト読み込み完了 function text_load_complete(){ // テキストエリアへ反映 var dom = document.getElementById( 'save_text' ); dom.value = ajaxRecvData; // ファイル一覧取得通信開始 getFileList(); } // テキスト保存実行 function textSave(){ var sendData; var text_val = document.getElementById('save_text').value; //console.log(dom); sendData = createQueryParam( "flg", 0, "text_val", text_val ); //console.log( sendData ); ajaxNetConnect( "./server.php", sendData, "POST", 1, "text_save_complete" ); } // テキスト保存完了 function text_save_complete(){ //console.log( "受信データ:"+ajaxRecvData+"" ); alert( "テキスト保存成功しました" ); } // ドラッグ要素がドロップ要素に重なっている間の処理 function f_dragover(event){ // dragoverイベントをキャンセルして、ドロップ先の要素がドロップを受け付けるようにする event.preventDefault(); } // ドロップ時の処理 function f_drop(event){ var file = event.dataTransfer.files[0]; //console.log( file.name ); // ファイル送信用AJAX var formData = new FormData(); formData.append( "flg", "2" ); formData.append( "file", file ); ajaxSendFormData( "./server.php", formData, "file_save_complete" ); // エラー回避のため、ドロップ処理の最後にdropイベントをキャンセルしておく event.preventDefault(); } // ファイル保存完了 function file_save_complete(){ console.log( 'file_save_complete' ); // ファイル一覧取得通信開始 getFileList(); } // ファイル一覧取得 function getFileList(){ sendData = createQueryParam( "flg", 3 ); ajaxNetConnect( "./server.php", sendData, "POST", 1, "file_list_complete" ); } // ファイル一覧取得完了 function file_list_complete(){ //ajaxRecvData var obj = document.getElementById( 'file_list' ); var str = ""; if( ajaxRecvData != "file_not_found" ){ var fileNames = ajaxRecvData.split( "," ); str += "<table border='1'>"; str += "<tr>"; str += "<td width='500px'>ファイル名</td>"; str += "<td>ダウンロード</td>"; str += "<td>削除</td>"; str += "</tr>"; for( var i=0; i<fileNames.length; i++ ){ // ファイル名からフォルダ名を除く var buff = fileNames[i].split( "/" ); var fileName = buff[2]; str += "<tr>"; str += "<td>"+fileName+"</td>"; str += "<td><a href='"+fileNames[i]+"' download>ダウンロード</a></td>"; str += "<td align='center'><a href='#' onClick='fileDeleteStart(\""+fileNames[i]+"\");'>■</a></td>"; str += "</tr>"; } str += "</table>"; obj.innerHTML = str; } else { str = "ファイルがありません"; obj.innerHTML = str; } //console.log( 'file_save_complete' ); } // ファイル削除 function fileDeleteStart( fileName ){ var buff = fileName.split( "/" ); var buffFileName = buff[2]; // ファイル削除確認ダイアログ表示 var value = window.confirm( "「"+buffFileName+"」を削除します、よろしいですか?" ); if( value ){ sendData = createQueryParam( "flg", 4, "file_name", fileName ); ajaxNetConnect( "./server.php", sendData, "POST", 1, "file_delete_complete" ); } } // ファイル削除完了 function file_delete_complete(){ if( ajaxRecvData == "file_delete_success" ){ alert( "ファイル削除成功" ); // ファイル一覧取得通信開始 getFileList(); } else { alert( "削除ファイルが見つかりませんでした" ); } } </script> </head> <body> [テキスト]<br> <textarea id="save_text" name="save_text" rows="10" cols="100"></textarea> <br> <input type='button' id='button' name='button' onClick='textSave()' value='保存'><br><br> [ファイル一覧]<br> <div id='file_list' name='file_list'> </div><br> [ファイルアップロード]<br> <div id='file_upload' name='file_upload' style='width:300px; height:100px; border:1px dotted #333333;' ondragover="f_dragover(event);" ondrop="f_drop(event);"> <p>ここにドラッグ</p> </div> </body> </html> コード解説 コードを見てもらえば分かるのですが、9割JavaScriptになっています。 また、このような小規模のシステムの時は極力ファイル数を増やしたくないので、サーバへのリクエスト時に「flg=0」といったパラメータを付与し、サーバ側ではフラグの値を見て「0:読み込み、1:保存」というように処理を分岐させるといったことをよくやります。 上記の観点でコードを見てもらえると処理が追いやすいかと思います。 続いて、Ajaxライブラリになります。 Ajax.inc //======================================================================================= // // AJAX // //======================================================================================= var xmlhttp; // XMLHttpオブジェクト var ajaxRecvData; // 受信データ var HTTP_GET = "GET"; var HTTP_POST = "POST"; var HTTP_RECV = 1; //----------------------------------------------------------- // getXMLHttp : XMLHttpオブジェクト取得 // 引数 : 無し // 戻り値 : XMLHttpオブジェクト //----------------------------------------------------------- function getXMLHttp() { // ブラウザによって処理を変える if( window.XMLHttpRequest ){ xmlhttp = new XMLHttpRequest(); } else if( window.ActiveXObject ){ try { xmlhttp = new ActiveXObject( "Msxml2.XMLHTTP" ); } catch( e ){ xmlhttp = new ActiveXObject( "Microsoft.XMLHTTP" ); } } return xmlhttp; } //----------------------------------------------------------- // createQueryParam : クエリパラメータ作成 // 引数 : 名前,パラメータ(可変長引数) // 戻り値 : 成功・・クエリパラメータ // 戻り値 : 失敗・・false //----------------------------------------------------------- function createQueryParam() { var param = new Object(); var key; var array = new Array(); if( arguments.length % 2 != 0 ){ // 引数が2の倍数でなければエラー alert( "エラー:クエリパラメータは[名前:値]のセットで指定して下さい" ); return false; } // クエリパラメータを作成 for( i=0; i<arguments.length; i+=2 ){ param[arguments[i]] = arguments[i+1]; } for( key in param ){ array.push( key + "=" + encodeURIComponent(param[key]) ); } array = array.join( "&" ); // クエリパラメータを&で結合する return array; } //----------------------------------------------------------- // ajaxNetConnect : データ送受信 // 引数 : URL,送信データ,通信種別(GET,POST) // 引数 : 送受信フラグ,コールバック関数名 // 戻り値 : 無し //----------------------------------------------------------- function ajaxNetConnect( url, sendData, sendKind, sendFlg, funcName ) { if( xmlhttp == null ){ getXMLHttp(); // XMLHttpオブジェクト取得 if( xmlhttp == null ){ alert( "エラー:XMLHttpオブジェクトがありません" ); return; } } // 通信種別によって処理を変更 if( sendKind == HTTP_GET ){ // GET通信 if( sendData != null ) url += "?"+sendData+""; // URLの後ろにリクエストパラメータを付与 } xmlhttp.open( sendKind, url, true ); // オープン if( sendKind == HTTP_POST ){ // POST通信 xmlhttp.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" ); // ヘッダーセット xmlhttp.send( sendData ); } if( sendFlg == HTTP_RECV ){ // 受信の時のみ処理 ajaxRecvData = null; // 受信データ初期化 } xmlhttp.onreadystatechange=function() { if( xmlhttp.readyState == 4 ){ // 準備完了 if( xmlhttp.status == 200 ){ // 成功 if( sendFlg == HTTP_RECV ){ // 受信の時のみ処理 ajaxRecvData = xmlhttp.responseText; // 受信データを保存 } } else { // 失敗 if( sendFlg == HTTP_RECV ){ // 受信の時のみ処理 ajaxRecvData = "error"; } } if( funcName != null ){ // コールバック関数名がある場合のみ処理 eval( funcName + '()' ); // コールバック関数実行 } } }; // GET通信の場合はここで送信 if( sendKind == HTTP_GET ) xmlhttp.send( null ); } //----------------------------------------------------------- // ajaxSendFormData : フォームデータ用Ajax通信 // 引数 : URL,送信データ,コールバック関数名 // 戻り値 : 無し //----------------------------------------------------------- function ajaxSendFormData( url, sendData, funcName ) { if( xmlhttp == null ){ getXMLHttp(); // XMLHttpオブジェクト取得 if( xmlhttp == null ){ alert( "エラー:XMLHttpオブジェクトがありません" ); return; } } ajaxRecvData = null; // 受信データ初期化 xmlhttp.open( 'POST', url ); xmlhttp.send( sendData ); xmlhttp.onreadystatechange=function() { if( xmlhttp.readyState == 4 ){ // 準備完了 if( xmlhttp.status == 200 ){ // 成功 ajaxRecvData = xmlhttp.responseText; // 受信データを保存 } else { // 失敗 ajaxRecvData = "error"; } if( funcName != null ){ // コールバック関数名がある場合のみ処理 eval( funcName + '()' ); // コールバック関数実行 } } }; } コード解説 Ajax通信を行うだけのライブラリなのですごくシンプルです。 少し特徴的な部分だけ解説します。 ■createQueryParamメソッド クエリパラメータの文字列を作成するメソッドなのですが、パラメータ数は通信の種類によって変化するために引数が可変長引数(arguments変数)になっています。 ■ajaxNetConnectメソッド Ajax通信を行っているメソッドです。 業務ではよくjQueryを使用してAjax通信処理を実行することが多いと思うのですが、素のJavaScriptでAjax通信を記述するとこんな感じになります。 第5引数の「funcName」でメソッド名を渡しているのですが、サーバからのデータ受信後にevalメソッドを実行することで、指定した引数(文字列)のメソッドを実行させることが出来ます。 こうすることで「通信A終了後はAメソッドを実行」「通信B終了後はBメソッドを実行」といった形でそれぞれ別メソッドに定義することが出来るようになります。 サーバ側 サーバ側のソースは以下になります。 server.php <?php $response = "responce_no_data"; $textFileName = "./file.txt"; $folderName = "./file"; $flg = $_REQUEST['flg']; switch( $flg ){ case 0: // テキスト保存 $text_val = $_REQUEST['text_val']; $text_val = mb_convert_encoding( $text_val, "Shift-JIS", "UTF-8" ); // テキストファイル保存 if( file_exists($textFileName) ){ unlink( $textFileName ); } file_put_contents( $textFileName, $text_val ); // レスポンス $response = "保存成功"; break; case 1: // テキスト読み込み $response = file_get_contents( $textFileName ); $response = mb_convert_encoding( $response, "UTF-8", "Shift-JIS" ); break; case 2: // ファイル保存 if( isset($_FILES['file']) ){ $file = $_FILES['file']; $fileName = $file['name']; // ファイル名 $copyFolder = "".$folderName."/".$fileName.""; // コピーフォルダ名 move_uploaded_file( $_FILES['file']['tmp_name'], $copyFolder ); } break; case 3: // ファイル一覧取得 $result = glob( "".$folderName."/*" ); if( !empty($result) ){ // ファイルが存在する $response = ""; for( $i=0; $i<count($result); $i++ ){ if( $i > 0 ) $response .= ","; $response .= $result[$i]; } } else { $response = "file_not_found"; } break; case 4: // ファイル削除 $deleteFileName = $_REQUEST['file_name']; if( file_exists($deleteFileName) ){ unlink( $deleteFileName ); $response = "file_delete_success"; } else { $response = "file_not_found"; } break; } echo $response; コード解説 サーバ側のコードはとてもシンプルになっています。 特にPHPはファイル保存や移動などが数行のコードで実現出来るのでとても便利です。 クライアント側でも書きましたが「flg」変数で分岐させているので処理が見通しやすくなっていると思います。 最後に とてもシンプルなツールですが、Ajax通信の連続実行(ページ読み込み時)やドラッグ&ドロップのイベント処理など、業務に利用出来るような部分もあるかなと思います。 また前回の内容と組み合わせると、外部からアクセス出来るツールにすることも出来るので是非チャレンジしてみて下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

The PutObject operation requires non-empty parameterのエラー対応について

下記の記事を参考にs3へ画像を保存する途中でThe PutObject operation requires non-empty parameterのエラーが出たのでその対処法です。 参考にした記事 確認手順 上記記事を参考にcomposerで必要なものを入れたりバケットを作ったり.envを書き換えます。 その後、 php artisan config:cache もしくは php artisan cache:clear を実行 config/filesystems.phpの設定確認 disk arrayのs3の部分を見る .envと比較して.envをきちんと反映しているか確認 実行してみて下記でデバック MainController.php use Config; 省略---- dd(Config::get('filesystems')); s3の部分が反映されていない場合にはconfig/filesystems.phpに直接記入してみて動けばcacheがうまく消えていない可能性が高いです。 感想 自分は古いcacheがうまく消えていなかったのが問題でした。 php artisan cache:clearだけではうまく消えないらしくphp artisan config:cacheを打つとうまく反映されました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む