【PHP】ある特定のAPIへリクエストすると勝手にリダイレクトされる
結論
リダイレクト時のステータスコードは 301
ではなく 302
を使う!
※もしくは明示的に指定しなければデフォルトで302になる
前提
一般ユーザーAでログインしているときに /user
へリクエストすると、制限された情報を返すAPIがある。
一方、Adminユーザーでログインしているときに /user
へリクエストすると、API側でAdminであれば /admin/user
へリダイレクトさせる処理があった。
このときにリダイレクト処理を下記のようにしてしまっていた。
<?php if ($user_id === ADMIN_USER_ID) { header("Location: /admin/user", true, 301); exit; }
再現手順
- Adminユーザーでログインして
/user
へリクエストすると、API側で/admin/user
にリダイレクトされる。(想定された挙動) - Adminユーザーからログアウト
- 一般ユーザーAでログイン
/user
へリクエストすると、/admin/user
へなぜかリダイレクトされる。/admin/user
自体はAdminユーザーでないと認可していないので、 ステータスコードは403
で返ってくる。(想定されていない挙動)- 想定されたレスポンスが返ってこないのでエラーが出る。
原因
301
でリダイレクトさせるとブラウザ側でキャッシュしてしまうことが原因っぽい。
ちゃんとドキュメントに書いてあった。
A quick way to make redirects permanent or temporary is to make use of the $http_response_code parameter in header().
<?php // 301 Moved Permanently header("Location: /foo.php",TRUE,301); // 302 Found header("Location: /foo.php",TRUE,302); header("Location: /foo.php"); // 303 See Other header("Location: /foo.php",TRUE,303); // 307 Temporary Redirect header("Location: /foo.php",TRUE,307); ?>
The HTTP status code changes the way browsers and robots handle redirects, so if you are using header(Location:) it's a good idea to set the status code at the same time. Browsers typically re-request a 307 page every time, cache a 302 page for the session, and cache a 301 page for longer, or even indefinitely. Search engines typically transfer "page rank" to the new location for 301 redirects, but not for 302, 303 or 307. If the status code is not specified, header('Location:') defaults to 302.
参考
【React】基礎のポイント振り返り
Reactの勉強記録。 こちらのトラハックさんのReact入門基礎編の振り返り
コンポーネント
見た目と機能を持つUI部品
大きく2つに分かれる
コンポーネントのいいところ
- 再利用するため
- 同じ記述を何度もする必要がない
- コードの見通しをよくするため
- 1コンポーネント=1ファイル
- 責務を明確にする(何のためのパーツなのか)
- 別ファイルに分ける
- 変更に強くするため
- 修正は1箇所
基本的な使い方
//App.jsx(親) import Article from "./components/Article"; function App() { return ( <div> <Article /> </div> ) } export default App;
// components/Article.jsx(子) const Article = () => { // アロー関数バージョン return <h2>こんにちは</h2> }; export default Article;
propsでデータを受け渡す
//App.jsx(親) import Article from "./components/Article"; function App() { return ( <div> <Article title={'勉強中'} content={'propsについて'} /> </div> ) } export default App;
// components/Article.jsx(子) const Article = () => { return ( <div> <h2>{props.title}</h2> <p>{props.content}</p> </div> ) }; export default Article;
- 子コンポーネントの引数に
props
を指定する - 親から子にデータを渡す
#コンポーネントのimportとexport
JavaScriptのモジュール機能
- プログラムをモジュールという単位に分割する
- 原則、1ファイル=1モジュール
- 必要なときに必要なモジュールのみ読み込む
default export(名前なしexport)
// アロー関数 const Title = (props) => { return <h2>{props.title}</h2> }; export default Title;
// 名前付き関数 export default function Title(props) { return <h2>{props.title}</h2> };
- 推奨されるexport方法
- 1ファイル=1export
- 一度宣言したアロー関数をdefault export
- 名前付き関数宣言と同時にdefault export
default import(名前なしimport)
// Article.jsx(export元) const Article = (props) => { return ( <div> <h2>{props.title}</h2> <p>{props.content}</p> </div> ); }; export default Article;
// App.jsx(import先) import Article from "./components/Article"; function App() { return ( <Article title={'タイトル'} content={'中身'} /> ); };
- default exportしたモジュールをそのまま読み込む
名前付きexport
// helper.js(1ファイルに便利関数が複数入っている) export const addTax = (price) => { return Math.floor(price * 1.1) } export const getWild = () => { console.log('Get wild and touch') } // index.js export {default as Article} from './Article' export {default as Content} from './Content' export {default as Title} from './Title' // 別名(エイリアス)はなんでもいいけどファイル名にするのが一般的
- 1ファイルから複数モジュールをexportしたいとき
- Reactではエントリポイントでよく使う
- エントリポイントでは別名exportも併用する
名前付きimport
// 直接コンポーネントを読み込むのではなく、エントリポイント(index.jsx)を通してimport import {Content, Title} from "./index.jsx"; const Article = (props) => { return ( <div> <Title title={props.title} /> <Content content={props.content} /> </div> ); }; export default Article;
- 1ファイルから複数モジュールを読み込む
- エントリポイントから複数コンポーネントを読み込む
コンポーネントの状態管理
Hooks
なぜstateを使うのか
- Reactコンポーネント内の値を書き換えたい
- ❌:DOMで直接書き換える
- ⭕️:新しい値を使って再レンダリングさせる
- Reactコンポーネントが再レンダリングするきっかけは?
- stateが変更されたとき
- propsが変更されたとき
- 子から親にpropsを渡すことはできない
- 親で更新用の関数を用意しておいて、子でその更新用の関数を実行して、親のpropsを変更させる
useStateの使い方
- userStatueによるstateの宣言
const [state, setState] = useState(initialState) 現在の状態 更新関数 初期値
- stateの更新
setState(newState) 更新関数 新しい値
- 具体例
const [message, setMessage] = useState('test!!') const [links, setLinkes] = useState(0) const [isPublished, setIsPublished] = useState(false) setIsPublished(true)
stateをpropsに渡す
// Article.jsx const Article = (props) => { const [isPublished, setIsPublished] = useState(false) const publishArticle = () => { setIsPublished(true) } return ( <div> <Title title={props.title} /> <Content content={props.content} /> <PublishButton isPublished={isPublished} onClick={publishArticle} /> </div> ); };
// PublishButton.jsx const PublishButton = (props) => { return ( <button onClick={() => props.onClick()}> 公開状態: {props.isPublished.toString()} </button> ); } export default PublishButton;
- 更新関数はそのままpropsとして渡さず関数化する
- 関数をpropsに渡す時は注意する
propsへ関数を渡す際の注意点
- OKな関数の渡し方
- コールバック関数化関数自体を渡す
<PublishButton isPublished={isPublished} onClick={publishArticle} /> // コールバック関数 <PublishButton isPublished={isPublished} onClick={() => publishArticle()} />
- NGな関数の渡し方(無限レンダリングが起こる)
- propsに渡すときに関数を実行しない
// ()をつけて関数を実行してしまっている <PublishButton isPublished={isPublished} onClick={publishArticle()} />
頻出するuseStateの使い方
引数を使って更新する
import React, {useState} from 'react'; const TextInput = () => { const [name, setName] = useState('') const handleName = (event) => { setName(event.target.value) } return ( <input onChange={(event) => handelName(event)} // eventはonChangeイベントの戻り値 type={'text'} value={name} /> ); }; export default TextInput;
- 入力フォームでよく使う
- onChangeイベントでhandleName関数に渡す
- handelName関数のパラメータであるeventを更新関数に渡す
prevStateを活用する
import React, {useState} from 'react'; const Counter = () => { const [count, setCount] = useState(0) const countUp = () => { setCount(prevState => prevState + 1) } const countDown = () => { setCount(prevState => prevState - 1) } return ( <div> <p>現在のカウント数: {count}</p> <button onClick={countUp}>up</button> <button onClick={countDown}>down</button> </div> ); }; export default Counter;
- useStateの更新関数で使える特殊はprevState
- prevStateは更新前のstate
- prevStateに変更を加えてreturn
こっちの書き方は良くない!
import React, {useState} from 'react'; const Counter = () => { const [count, setCount] = useState(0) const countUp = () => { setCount(count + 1) } const countDown = () => { setCount(count - 1) } return ( <div> <p>現在のカウント数: {count}</p> <button onClick={countUp}>up</button> <button onClick={countDown}>down</button> </div> ); }; export default Counter;
- countを直接書き換えている
- 更新をしますとしてから、実際に更新されるまで多少のタイムラグが起こり、正しく変更されない(非同期で変更するため、実際に更新されるまでは前回の状態を参照している)
ON/OFFを切り替えるボタン
import React, {useState} from 'react'; const ToggleButton = () => { const [open, setOpen] = useState(false) const toggle = () => { setOpen(prevState => !prevState) } return ( <button onClieck={toggle}> {open ? 'OPEN' : 'CLOSE'} </button> ); }; export default ToggleButton;
- prevStateで受け取った値を ! で反転してreturnする
- 三項演算子によってopenがtrue/falseで表示を切り替える
ライフサイクルと副作用(useEffect)
ライフサイクルとは
- コンポーネントが生まれてから破棄されるまでの時間の流れ
- ライフサイクルメソッドを使うと、時点に応じた処理を実行できる
- Class Component時代は以下の3メソッドが頻出だった
- componentDidMout()
- componentDidUpdate()
- componentWillUnmount()
- Hooks時代はuseEffectでライフサイクルを表現
3種類のライフサイクル
副作用(effect)フックを使おう
constCounter = () => { const [count, setCount] = useState(0) const countUp = () => { setCount(prevState => prevState + 1) } const countDown = () => { setCount(prevState => prevState - 1) } // countというstateが変更されるたびに再レンダリング起きて、その度に起こされる処理 useEffect(() => { console.log("Current count is...", count) }) return ( <div> <p>現在のカウント数: {count}</p> <button onClick={countUp}>up</button> <button onClick={countDown}>down</button> </div> ); };
第二引数の依存関係を理解する
// 毎回実行される useEffect(() => { console.log("Current count is...", count) }) // 初回レンダリング後のみ実行される useEffect(() => { console.log("Current count is...", count) }, []) // triggerが変更されるたびに実行される useEffect(() => { console.log("Current count is...", count) }, [trigger]) // trigger1かtrigger2が変更されるたびに実行される useEffect(() => { console.log("Current count is...", count) }, [trigger1, trigger2])
- useEffectの第二引数には配列を渡すことが可能
- 第二引数はdeps(dependencies)と呼ばれ、副作用が引き起こされるどうかの依存関係となる
クリーンアップを理解する
const ToggleButton = () => { const [open, setOpen] = useState(false) const toggle = () => { setOpen(prevState => !prevState) } useEffect(() => { console.log("Current state is", open) if (open) { console.log("Subscribe database...") } return () => { console.log("Unsbscribe database!") } }) return ( <button onClieck={toggle}> {open ? 'OPEN' : 'CLOSE'} </button> ); };
- コンポーネントないで外部データベースを購読したい
- useEffectないで購読処理を呼び出す
- 必要無くなったらクリーンアップ関数を使って掃除する
- useEffectの中でreturnする
useEffect内でAPIを呼び出そう
useEffectのユースケース
- APIやデータベースから非同期通信でデータを取得(fetch)する
- 特定の値が変わったらデータを再取得(refetch)する
useEffect内で非同期通信
useEffect(() => { fetch(`https://api.github.com/users/${id}`) .then(res => res.json()) .then(data => { console.log(data) setName(data.name) }) .catch(error => { console.error(error) }) }, [id])
- 初回レンダリング後に呼び出される
- 第二引数に指定した値が変わるたびに再度呼び出される
- 取得した値をuseStateの更新関数に渡す
【React】create-react-appするまでの環境構築手順
Reactの学習記録
こちらを使って勉強中。まずは環境構築手順の記録
はじめに
- macで作業
create-react-app
を使ってお手軽に環境構築していく- このコマンドを使うためには、
node
とnpm
が必要。まずそれらをインストールする。 - チュートリアルにも書いてある(https://ja.reactjs.org/docs/create-a-new-react-app.html#create-react-app)
- このコマンドを使うためには、
Create React App は React を学習するのに快適な環境であり、React で新しいシングルページアプリケーションを作成するのに最も良い方法です。
開発環境をセットアップして最新の JavaScript の機能を使えるようにし、快適な開発体験を提供し、そして本番環境用の最適化を行います。あなたのマシンに Node >= 14.0.0 及び npm >= 5.6 の環境が必要です。プロジェクトを作成するには次を実行します:
概要
create-react-app
をするためにはnpm
が必要npm
をインストールするためにはnode
が必要node
を使うにはnodebrew
が必要nodebrew
をインストールするためにはhomebrew
が必要 だからまずはmacにhomebrew
をインストールするところから
手順
Homebrewをインストール
// インストールの確認 % brew -v Homebrew 4.0.1
nodebrewをインストール
% brew install nodebrew % nodebrew -v nodebrew 1.2.0
nodeをインストール
// インストールできるバージョン一覧を確認 % nodebrew ls-remote // 安定バージョンをインストール % nodebrew install-binary stable // エラーが出てインストールできなければ % nodebrew setup // もう一度 % nodebrew install-binary stable // インストールしたバージョンの確認 % nodebrew ls v18.14.1 current: none // インストールしたバージョンを使う % nodebrew use v18.14.1 // もう一度確認 currentが設定されている % nodebrew ls v18.14.1 current: v18.14.1
nodeの環境パスを通す
// パスを確認(シェルはzsh) % echo $SHELL /bin/zsh // パスを通す echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zprofile
環境パスを通したら必ずターミナルを再起動
nodeとnpmのバージョン確認。
% node -v v18.14.1 % npm -v 9.3.1
create-react-app
コマンドを使うのに 必要なバージョンを満たしているのでOK。作業フォルダへ移動
% cd workspace
npm
とは、ネットワーク上にあるnpm
のコマンドを実行するということ% npx create-react-app react-basic
ローカルサーバー起動
% cd react-basic % npm start
自動で localhost3000 でブラウザが開いて無事完了!
【PHP】BCMath関数を使わないで正確に小数点同士を計算する
結論
X倍して整数にしてから、結果をXで割る。
事象
PHPで小数点以下を含む値の引き算だと、二進数表現の精度が落ちる影響で、期待した値にならないことがある。
<?php echo(floor((0.1 + 0.7) * 10)); // 結果:7
8になるはずなのに、 floor()
で小数点以下を切り捨てると、なぜか7になる。
これは 0.1 + 0.7 の結果が、精度の誤差で0.799999999999999933386618522491…となるかららしい。
対策
BCMathをインストールして、 bcsub()
を使うと正しく計算できる、といろんな記事に書いてある。
でも、別途BCMathをインストールしてApachを再起動して〜てやると、少々面倒なのと影響範囲が大きかったので怖くて辞めた。PHPのバージョンも古かったので。
なので下記で対応。X倍して整数にしてから、結果をXで割る。
<?php $a = (0.1 * 10) + (0.7 * 10); echo($a / 10); // 結果: 8
参考
BCMathのインストール
ちなみに、別の環境でBCMathをインストールしたが、下記の記事が分かりやすかった。
【PHP】セッション名にドットを含めて指定するとセッションが取得できなくなる
課題
セッション名を指定して session_start()
すると、セッションが取得できなかったのでメモ。
結論
セッション名を指定するときに、「.」ドットは使わない!
例
セッション名を「.」ドットで繋いで session_start()
すると、セッションを取得できない。
<?php // lopgin.php session_start([ 'name' => 'test.com.session' ]); $_SESSION['user_id'] = $user_id;
<?php // test.php session_start([ 'name' => 'test.com.session' ]); var_dump($_SESSION); exit; // array (size=0) // empty
やるなら「_」アンダーバーで繋ぐこと。
<?php // lopgin.php session_start([ 'name' => 'test_com_session' ]); $_SESSION['user_id'] = $user_id;
<?php // test.php session_start([ 'name' => 'test_com_session' ]); var_dump($_SESSION); exit; // array (size=1) // 'user_id' => int 1
【PHP】CSV出力したいのにheader()関数が効かない
環境
やりたかったこと
PHPでCSVを出力する機能を作っていて、レスポンスヘッダーを下記のように設定したかった。
Content-Disposition: attachment; filename=ファイル名.csv Content-Type: text/csv
課題
下記のメソッドを使ってレスポンスしたのに、 header()
で設定したはずのが反映されていない。
使用した関数
<?php /** * CSVダウンロード * * @param string $file_name * @param array $header * @param array $csv_list * @return void */ public static function csvDownload($file_name, $header, $csv_list) { // 出力バッファをopen $stream = fopen('php://output', 'w'); // 文字化け対策 mb_convert_variables('SJIS-win', 'UTF-8', $header); // 列名を記入 fputcsv($stream, $header); // 2行目以降を1行ずつストリームへ記入 foreach ($csv_list as $line) { mb_convert_variables('SJIS-win', 'UTF-8', $line); fputcsv($stream, $line); } fclose($stream); // ファイルダウンロードさせるためにヘッダーを調整 header("Content-Type: text/csv"); header("Content-Disposition: attachment; filename=$file_name"); readfile($file_name); exit; }
Postmanで確認したレスポンスヘッダ
解決策
先程のメソッドで、 header()
関数を使う順番を最初に持ってきた
<?php /** * CSVダウンロード * * @param string $file_name * @param array $header * @param array $csv_list * @return void */ public static function csvDownload($file_name, $header, $csv_list) { // ファイルダウンロードさせるためにヘッダーを調整 header("Content-Type: text/csv"); header("Content-Disposition: attachment; filename=$file_name"); // 出力バッファをopen $stream = fopen('php://output', 'w'); // 文字化け対策 mb_convert_variables('SJIS-win', 'UTF-8', $header); // 列名を記入 fputcsv($stream, $header); // 2行目以降を1行ずつストリームへ記入 foreach ($csv_list as $line) { mb_convert_variables('SJIS-win', 'UTF-8', $line); fputcsv($stream, $line); } fclose($stream); readfile($file_name); exit;; }
すると、想定通りにレスポンスヘッダに反映されていた。
公式にも記載の通り、メソッド内でも先に書くようにということか?
覚えておいて頂きたいのは、header() 関数は、 通常の HTML タグまたは PHP からの出力にかかわらず、すべての実際の 出力の前にコールする必要があることです。 https://www.php.net/manual/ja/function.header
【Laravel】バリデーション失敗時に勝手にリダイレクトしないようにする(jsonを返させる)
課題
バリデーション失敗時に勝手に意図しないhtmlレスポンスを返す。
解決の過程
- ReactとLaravelで開発中に、データ更新時と登録時にたまに302レスポンスが返ってくる。
- コントローラまで処理が到達していない
- フォームリクエストで止まっているっぽい
- バリデーションに引っかかった時に302エラーを返しているよう
- ググったら、バリデーション失敗時は一個前のリクエストのページにリダイレクトしようとしているらしい。つまり、jsonで指定しているのにhtmlレスポンスになる。
- 勝手に前のページにリダイレクトしないようにしよう!
解決方法
Laravelはデフォルトで、バリデーション失敗時はリダイレクトされてHTMLレスポンスが返ってくるようになっているらしいので、このリダイレクトさせる処理をオーバーライドしてJSONが返ってくるようにする。
Illuminate\Foundation\Http\FormRequest
クラスを継承した
app/Http/Requests/ApiRequest.php
を作成して、バリデーション失敗時の処理をオーバーライドさせる。
<?php namespace App\Http\Requests; use Illuminate\Contracts\Validation\Validator; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Http\Exceptions\HttpResponseException; class ApiRequest extends FormRequest { /** * @Override * 勝手にリダイレクトさせない * @param \Illuminate\Contracts\Validation\Validator $validator */ protected function failedValidation(Validator $validator) { $data = [ 'message' => 'The given data was invalid.', 'errors' => $validator->errors()->toArray(), ]; throw new HttpResponseException(response()->json($data, 200)); } }
使用例
フォームリクエストを使用する際は、デフォルトの FormRequest
を継承するのではなく、上記のオーバーライドした ApiRequest
を継承するようにする。
そうすれば、バリデーションに失敗してもJSONを返すようになる。
<?php use App\Http\Requests\ApiRequest; // 追記 #use Illuminate\Foundation\Http\FormRequest; // 使用せず class SampleRequest extends ApiRequest // 継承先をデフォルトのFormRequestから変更 { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'name' => ['required'], ]; } }
参考
Laravel5.8 FormRequestでバリデーション失敗時にJsonResponseを返す - Qiita
FormRequestのバリデーションエラー時のリダイレクト先をカスタマイズする - Qiita