- 投稿日:2020-01-22T23:14:55+09:00
クラスについて
用語
説明 プロパティ クラス内の変数 メソッド クラス内の関数 クラス メソッド、クラスの集まり(設計書) アクセス修飾子 どこからアクセスできるか指定 インスタンス クラスを具現化したもの アロー演算子 インスタンスしたプロパティやメソッドにアクセスするため スコープ演算子 クラスのプロパティやメソッドにアクセスするため クラス
設計書のようなもの
クラス = プロパティ + メソッド
データの保持 データの処理 変数 ◯ ✖️ 関数 ✖️ ◯ クラス ◯ ◯ クラスの定義class クラス名 { $プロパティ function メソッド(引数) { } }アクセス修飾子
どこからアクセスできるのかを指定するもの
アクセス修飾子を指定しないと、publicの扱いになる
種類 説明 public どこからでもアクセス可能 private クラス内のみアクセス可能 protected クラス内とそノクタスを継承した子クラスからアクセス可能 インスタンス
クラスをもとにしてものを作ること
(例)設計書(クラス)があれば車(インスタンス)がいくつも作れるインスタンス作成$変数名 = new クラス名(引数)インスタンス化したら基本的に変数に代入する
アロー演算子
インスタンスのプロパティやメソッドにアクセスするには、-> を使う
アロー演算子$インスタンスを代入した変数名->呼び出したいインスタンスのプロパティまたはメソッド名クラスの定義class Car { public function drive() { echo '走ります'; } } // インスタンスを作成(インスタンス化) $prius = new Car(); // アロー演算子でインスタンスメソッド($prius)へアクセス $prius->drive();スコープ演算子
クラスのプロパティやメソッドにアクセスするためには、::を使う
スコープ演算子クラス名::呼び出したいクラスのプロパティまたはメソッド名クラスのプロパティとメソッドについて
クラスのプロパティまたはメソッドにするには、staticという修飾子を付与する
プロパティであれば、プロパティ名の前にstaticを記述
メソッドであれば、functionの前にstaticを記述
staticプロパティへは、クラス名にダブルコロン(::)をつけてアクセスクラスのメソッド、プロパティclass クラス名 { アクセス修飾子 static $プロパティ名; アクセス修飾子 static function メソッド名(引数) { //処理を記述 } }staticの意味は?
クラス内のプロパティを扱う時は、一回インスタンス化をしなければいけないが、インスタンス化なしでクラスのメゾット内のプロパティにアクセスできている。スコープ定義演算子を使うことでコードの簡略化ができる
$this
クラス内のプロパティにアクセスできる
$this->プロパティ名
同じ変数が定義されても、thisを使えばクラスのプロパティにアクセスできるclass SamuraiClass{ public $val = 'Samurai'; //メソッドの宣言 public function SamuraiMethod(){ $val = 'Engineer'; //プロパティにアクセス echo $this->val; } } //インスタンスの生成 $class = new SamuraiClass(); //メソッドの呼出し $class->SamuraiMethod();//Samurai ?>コンストラクタ
アンダースコア2つ(__)で始まる
コンストラクタは、オブジェクトを生成するうえで必要なパラメータや、そのクラスのオプションなどを引数として受取り、プロパティにセットするという役割を果たす。コンストラクタclass person { public $name, $address, $age; function __construct($name, $address, $age) { $this->name = $name; $this->address = $address; $this->age = $age; } } class Employee extends person { public $position, $salary; function __construct($name, $address, $age, $position, $salary) { parent::__construct($name, $address, $age); $this->position = $position; $this->salary = $salary; } }
- 投稿日:2020-01-22T22:55:37+09:00
[個人開発]大喜利サービスを5日でつくってわかったこと
サービスイメージ
ひとこと大喜利「ザブトン」
https://zabuton.co/
誰でもすぐに簡単に大喜利が投稿でき、
SNS主体で大喜利を楽しむことができる
そんな大喜利サービスを開発しました。はじめに
個人開発でサービスはいくつも作ってきましたが
こういった記事を書くのは初めてです。後半の方でコードもGithubにて公開していますが、
文章、コード共に拙い部分がありますが、何卒お目溢し頂ければと思います。筆者について
僕は元々はゲーム会社出身ですが、
メンタルやられて退職してからフリーランスとなり
サービス乱立したりしながら生計を立てている20代エンジニアです。ただ、エンジニアという職業は手段であって、
僕の目的はものづくり(サービスつくり)です。今までは外界との接触が苦手で
TwitterやGithub、QiitaやQrunchといったサービスなどを
利用して自分から発信することはなかったのですが
一人での活動に色々と限界を感じてきて
自分の活動内容やサービスを公開していこうという運びとなります。1月からTwitterも初めてみました。もしよろしければどうぞ。
つくったサービス詳細
ひとこと大喜利「ザブトン」
https://zabuton.co/最初にも記載しましたが、大喜利サービスをつくりました。
このサービスのポイントは3つあります。
・大喜利の"回答"のみに特化したサービスサイト
・回答はTwitterへシェアする前提の設計
・OGPでお題、回答が概観できることこのサービスは大喜利の回答(コンテンツ)をTwitterへのシェアありきで作った、というところがポイントです。
競合大手の大喜利サービスではプラットフォームとしてコミュニティを形成させています。
(サービス内で閲覧→回答→閲覧のフローを促し回遊させ、継続させている)
ただ、結局のところコンテンツがどういった経路を辿り人々の目に触れていくかといえば、
結局はSNSだったりの影響が大きいのかと感じています。そこで、コンテンツをサービスサイト内で流通させることなく、
Twitterでの流通に限定するようなサービスフローにしたらよいのではないか、という発想でこのサービスをつくりました。・大喜利の"回答"のみに特化したサービスサイト
・回答はTwitterへシェアする前提の設計
・OGPでお題、回答が概観できることはじめにあげた3点のうち、最初の2点については上記の発想からの理由で、
最後の1点については、このようなサービスフローにするということは、
Twitter上でもストレスなく1つのTwitter上のコンテンツとして閲覧できるような
設計にすべきだという理由からです。なぜ作った
僕の目的はサービスづくりであり、
多くの人に利用してもらい、売上を立てることです。それを前提とした上でザブトンを作った理由は、二つです。
- 市場が大きいこと
- 既存サービスへのUX不満一つめから説明します。
・市場が大きいこと
これはそのままの意です。
いまだに、某大手大喜利サービスにおけるコミュニティの活発性や
ニュースアプリ、まとめ系ブログなど様々な場所で取り上げられています。
日本のテレビ番組がお笑い一色なことからもわかるように
日本人の性質として"お笑い"に関して興味が高いことがわかります。基本的に僕がサービスを作るときには
ニッチゆえに競合がいないブルーオーシャンは避け
必ず、競合もいるが需要が確立されているレッドオーシャンを攻めます。ニッチな業界でのグロースハックはとんでもない根気と労力がかかります。
そもそも市場にユーザー数が少ないうえに、新しいサービス利用に対しての敷居の高さがあるからです。なのであれば、レッドオーシャンだが需要(ユーザー)が溢れている市場から0.01%を取りに行くだけの方が、僕としてははるかにやりやすい。
そういった理由があります。
・既存サービスへのUX不満
かくいう僕もお笑いは大好きです。
くすっと笑える小ネタも、腹を抱えて笑える漫才も、
人の心を暖かくする、とてもハッピーなものだと考えています。そこで、現在一強であるお笑い系サービス"bo●●te"さんですが、
実際に投稿者側として利用しようとするととんでもなく使いにくいです。例えば、「ちょっと面白いお題を思いついた!」という場合にboketeを検索します。
選択肢は2つ。
1:アプリ
2:Web
まずはアプリから試してみました。
アプリをインストールし、開いてみるとまず「ログイン画面」。
まず会員登録しないと進めません。この時点で断念。次にWebを試してみました。
TOP→お題一覧→お題詳細→このお題でボケる(おっ!)→ボケたりお題投稿するには、ログインするか新規登録が必要です。
そうです。お題を登録したりボケたりするには会員登録が必須なのです。
しかもSNSログインもなし、いまどきメールアドレスを登録させるという苦行。これには僕も膝から崩れ落ちました。
これではお笑いが廃れてしまう、と。ただ、b●ke●eさんのサービス性を否定するわけではありません。
なぜならば、サービス内におけるコミュニティはかなり強固なものになっているし、
そのコミュニティに所属する人たちによっては大きな心の拠り所だと感じるからです。
そもそもこういったネットにおける大喜利が普及したのは、このサービスのおかげでしょう。ただ現代のインターネット属性に合致しているかという部分で非常に疑念を抱いてしまったので
下記3点をポイントとして掲げ、サービス開発へと乗り出した経緯があります。・大喜利の回答のみに特化したサービスサイト
・回答はTwitterへシェアする前提の設計
・OGPでお題、回答が概観できること制作スケジュール
工程 工数 企画構想 3時間 ワイヤー作成 30分 デザイン 4時間 コーディング 5時間 インフラ&バックエンド 4日 TOTALで3日くらいで作りたかったのですが、
他作業もちょこちょこしたりして結局TOTAL工数としては一週間弱かかってしまいました。
あとは、OGP画像の自動生成ロジックになかなか手こずってしまいました。
企画〜コーディングについては慣れたものでトータル1日ちょっとでした。
ただ、かの有名なpeing(質問箱)を作ったせせりさんは、
peingを6時間で作ったとのことなのでまだまだ精進のしがいがあるなというところです。なお、僕の場合、とにかく早く作って出すということを大前提にしているため
こういったスケジュール感になりがちです。余談ですが、Facebookのマークザッカーバーグの言葉で好きなのがこちら。
「完璧を目指すよりまず終わらせろ」途中経過
ワイヤー
※ちなみにこれは違うサービスのワイヤーです。ザブトンのものはデザインで上書きしてしまって、残してありませんでした...すみません。ちなみに、チームワークの場合と個人開発の場合では「ワイヤーフレーム」の利用用途は異なるのを前提として
僕の場合は、ワイヤーはこんな感じで機能要素だけをドカッと置いて、画面概要の確認にとどめます。
これにより工数の詳細な算出、必要な技術の確認を改めてしていきます。デザイン
デザインは、そのままコーディング出来るレベルまで詰めます。
だいたい他サービスの同じコンテンツブロックを参考にさせていただきながら作っていきます。正直、僕くらいのデザインレベルだとこだわっても対してクオリティ変わらないので、速度重視で作っていきます。
コーディング
デザインだけではわからなかった、要素を追加していきながらコーディングしていきます。
今回の場合、ヘッダーをTOPにつけるかというのはデザイン時点ではやめていたんですが、
実際に触ってみるとヘッダーがないとめちゃくちゃ不便だったため取り付けています。あとは、僕の場合XDを使ってワイヤーからデザインまで組みますが、
XDはカラーマネジメントしてくれずWebに乗せると色が変わってしまうので、
コーディングするときの感覚で色なんかも変えていっています。利用技術
ホスティングはAWS
Linux(Amazon Linux)
Apache
MySQL
PHP
フレームワークはcakephp3系
Html(css/js)今回は最低限しか利用していません。
サービスとしてログインや会員登録がなかったので、
サードパーティのAPIやライブラリもほとんど使ってません。
画像の合成にはImagineを利用しています。ソース
https://github.com/nsk-dev-gh/zabuton
こちらにまとめてあります。
Cakephp3系の
src配下とwebroot(img/css/js/font)配下をアップしておきます。
利用ライブラリは前述の通りImagineのみです。今回、Serviceを利用せずにController内でサービス処理を記載していたり
画像合成まわりの関数の可読性が最悪だったりするのですが
もしご参考になれば自由にご利用ください。ControllerはPagesのみを利用しています。
app.phpで下記記述をすることでURLからコントローラー名を排除しています。
$routes->connect('/', ['controller' => 'Pages', 'action' => 'index']);
$routes->connect('/:action', ['controller' => 'Pages']);
まとめ
今回はサービスリリースを目処として、この記事公開とさせていただきました。
ただ、広告を貼ってることからも収益が出始めた場合には、
アクセス数の情報や、売上なども公開していく予定です。他にもサービス作りを頻繁に発信していく予定ですので
改めてになりますが、よろしければTwitterフォローの方もよろしくお願いします。長々とした内容をここまでご拝読いただき
ありがとうございました!ひとこと大喜利「ザブトン」
https://zabuton.co/
- 投稿日:2020-01-22T22:47:27+09:00
⛰【Vue.js】MariaDBのJSON型カラムから取得したデータの指定の値を変数名で取り出す為にパースする
環境
Vue.js 6.12.0
PHP 7.3.10
Laravel 6.5.0
MariaDBやりたいこと
MariaDBのに下記のようなデータが格納されていてそれをLaravelのAPIで取得する。
その内のcontentカラム(JSON型)のデータからdata_kindの値を判断基準として条件分岐を行い、
valueの内容である「テストの内容です?」や「赤文字で強調する!!」や円周率をVueので表示する為にJSONをパースする
id content del_flg 1 {"data_kind": "1", "value": "テストの内容です?"} 0 2 {"data_kind": "2", "value": "赤文字で強調する!!"} 0 3 {"data_kind": "3", "value": "円周率を表示"} 0 4 {"data_kind": "4", "value": "削除済み???"} 1 やったこと
下に全体で色々書いてますが一番重要なのは下記のパース部分です
これが無いとJSONではなくJSONの形をしたただの文字列としてデータが扱われるので値を取り出すことができません// 取得データをforEachで全てJSON.parseにかける res.data.recordList.forEach(value => { value["content"] = JSON.parse(value["content"]); });全体
Hoge.vue// テンプレ部分 <template> <section class="container"> <div v-for="data in tableData" :key="content.id"> <!-- 通常表示 --> <div v-if="data.content.data_kind === '1'">{{ data.content.value }}</div> <!-- クラスを付けて装飾を変更 --> <div v-else-if="data.content.data_kind === '2'" class="redBold">{{ data.content.value }}</div> <!-- 円周率を表示 --> <div v-else-if="data.content.data_kind === '3'">3.141592653589323846....</div> </div> </section> </template> // データ取得とパース部分 <script> export default { data() { return { tableData: [] }; }, methods: { getContent() { axios .get("/api/getContent", { params: { delFlg: 0 } }) .then(res => { // 取得データをforEachで全てJSON.parseにかける res.data.recordList.forEach(value => { value["content"] = JSON.parse(value["content"]); }); this.tableData = res.data.recordList; }); } }, mounted() { this.getContent(); } }; </script> // 装飾 <style lang="sass" scoped> .redBold color: red font-weight: bold </style>
- 投稿日:2020-01-22T20:25:34+09:00
Moodle 3.8 マニュアル - Nginx
Nginx
Nginx [engine x] は Igor Sysoev 氏によって書かれた HTTP とメールプロキシサーバーのみならずリバースプロキシサーバーでもあります。nginx プロジェクトは高い同時並行性、高パフォーマンスそして低メモリ使用量に焦点をあててスタートしました。The 2-Clause BSD License の元でライセンスされ Linux、*nix フレーバー上でのみならず様々な BSD Mac OS X、Solaris、AIX、HP-UX 上で動作します。Microsoft Windows への移植のための概念実証もあります。
次はコミュニティの貢献による Nginx の設定についての文書です。修正や追加は歓迎です。
内容
1 Nginx の設定
1.1 PHP-FPM
1.2 Nginx
1.2.1 X-Accel-Redirect として知られる XSendfile
2 関連項目1 Nginx の設定
1.1 PHP-FPM
Nginx は通常は php-fpm を経由して PHP のインタフェースにより設定されます。これは速く堅牢です。
証明のための PHPーFPM のデフォルトの動作は通常特別な拡張子でのスクリプトの実行に限定されます、一例は php です。この挙動は特別なパッケージ/ディストリビューション中で設定されることを確実にしてください、debian の一例をあげれば、
/etc/php5/fpm/pool.d/www.conf
security.limit_extensions = .php
12 Nginx
あなたの vhosts 'server' の nginx 設定に次の 'slash arguments' 互換の 'location' ブロックを追加して(さらなる説明は 'Using slash arguments' にあります)ください。
nginx.conf の location:
location ~ [^/]\.php(/|$) { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_index index.php; fastcgi_pass 127.0.0.1:9000 (or your php-fpm socket); include fastcgi_params; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }もし上記で動作しない場合は次を試してください。注:これは CentOS 7.6 (1804), MariaDB 10.3, Nginx 1.15 と PHP 7.3.5 用でした。
location ~ ^(.+\.php)(.*)$ { root /usr/share/nginx/html/moodle/; fastcgi_split_path_info ^(.+\.php)(.*)$; fastcgi_index index.php; fastcgi_pass 127.0.0.1:9000; include /etc/nginx/mime.types; include fastcgi_params; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }もしこれで動作しない(スクリプト、スタイル、イメージがロードしない)場合で open() "..." failed (20: Not a directory) があなたのログに出力する場合は、このブロックの前にスタティックコンテントに関連するディレクティブがないかチェックしそれらをこのブロックの後で除いてみてください。
1.2.1 X-Accel-Redirect として知られる XSendfile
Moodle と Nginx を XSendfile 機能を使用するように設定することは ファイルを配布することを Nginx に許可することで PHP からそれ、すなわちファイルの配布を解放することになるので大きな勝利です。
Nginx のためにモジュール config.php で xsendfile を有効にすることは、config-dist.php においてドキュメント化されていますが、最低限の設定は以下のようになります。
$CFG->xsendfile = 'X-Accel-Redirect'; $CFG->xsendfilealiases = array( '/dataroot/' => $CFG->dataroot );これが nginx サーバー設定の 'localtion' ブロックにマッチすることにともなって、
location /dataroot/ { internal; alias <full_moodledata_path>; # ensure the path ends with / }ここの 'internal' 設定はあなたの dataroot へのクライアントからのアクセスを防ぐために重要です。
2 関連項目
Real PATH_INFO support:
https://moodle.org/mod/forum/discuss.php?d=278916
https://moodle.org/mod/forum/discuss.php?d=307388
[非推奨] Internal rewriting to the HTTP GET file parameter:
https://moodle.org/mod/forum/discuss.php?d=83445カテゴリ:インストール
- 投稿日:2020-01-22T20:10:11+09:00
よく使いそうなPHPでのデータベースへのアクセス
引用
ドットインストール PHPデータベース入門
自分用にメモしてます。DBのセットアップ
最初のセットアップmysql -u root create database new_db; grant all on new_db.* to admin@localhost identified by 'password'; use dotinstall_db; create table users ( id int not null auto_increment primary key, name varchar(255), score int ); mysql -u admin -p passwordPDOのセットアップ
DBの基本情報を定数で定義
define('DB_DATABASE', 'new_db'); define('DB_USERNAME', 'dbuser'); define('DB_PASSWORD', 'password'); define('PDO_DSN', 'mysql:host=localhost;dbname=' . DB_DATABASE);PDOは基本的にtryとcatchで記述
エラーが出た時にはExceptionを、setAttributeというメソッドで設定する。
PHPで定義済みのクラスを書くときは先頭に「\」を付ける習慣をつける。(※)
※名前空間を使っている場合、PHPに標準で用意されているException()やDateTime()のようなクラスに関しては一番上の名前空間から呼び出さなければならない。
php
try {
// connect
$db = new PDO(PDO_DSN, DB_USERNAME, DB_PASSWORD);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (\PDOException $e) {
echo $e->getMessage();
exit;
}
exec()
- 引数に与えられたSQL文を実行する。
- 返り値は更新・削除された行数を返す。作用しなかった場合は0を返す。
- SELECT文からは結果を返さない
// insert $db->exec("insert into users (name, score) values ('yamada', 28)"); echo "success!"; // disconnect $db = null;prepare() execute()
prepare()
- 複数回実行されるSQL、安全対策が必要なSQL文に適している。
- DBが正常に文を準備する場合は、PDOStatementオブジェクトを返す。もしDBが文を準備できなかった場合はFALSEかPDOExceptionを発行する(エラー処理の方法に依存)
execute()
- 配列型でパラメータを渡す。
- 何も指定しない場合は文字列で渡される。数値を渡したい場合はbindvalue,bindparam 型を指定する必要がある。
$stmt = $db->prepare("insert into users (name, score) values (?, ?)"); $stmt->execute(['taguchi', 44]); echo "inserted: " . $db->lastInsertId();名前つきパラメータ
扱うカラムが多くなってきたときに整理しやすくなる。
$stmt = $db->prepare("insert into users (name, score) values (:name, :score)"); $stmt->execute([':name'=>'hoge', ':score'=>80]); echo "inserted: " . $db->lastInsertId();bindvalue()
- 第一引数:パラメータid(1から始まる)
- 第二引数:バインドする値
- 第三引数:データの型
- 文字列:PDO::PARAM_STR
- 数値は:PDO::PARAM_INT
- これを使うとループなどで一部の値だけ違うレコードを一気に挿入できる。
- 名前付きパラメータの場合にも適用できる。
$name = 'yamada'; $stmt->bindValue(1, $name, PDO::PARAM_STR); // $stmt->bindValue(':name', $name, PDO::PARAM_STR); $score = 35; $stmt->bindValue(2, $score, PDO::PARAM_INT); $stmt->execute(); $score = 29; $stmt->bindValue(2, $score, PDO::PARAM_INT); $stmt->execute();bindParam()
- bindvalueは値をバインド、bindParamは変数への参照をバインド
- 実際に変数を参照するのはexecute()時。
$stmt->bindParam(2, $score, PDO::PARAM_INT); $score = 52; $stmt->execute(); $score = 44; $stmt->execute(); $score = 6; $stmt->execute();query() fetchAll() rowCount()
query()
- 何回も実行されないSQLに適する。
返り値はPDOStatementオブジェクトを返す。失敗した場合はFALSEを返す。
fetchAll()引数に返り値の形式を指定できる。
rowCount()
- 取得したレコードの数を取得
// select all $stmt = $db->query("select * from users"); $users = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($users as $user) { var_dump($user); } echo $stmt->rowCount() . " records found.";transaction
複数の処理により整合性を保つ必要がある場合に必要。
try { // transaction $db->beginTransaction(); //処理1 //処理2 $db->commit(); } catch (\PDOException $e) { $db->rollback(); echo $e->getMessage(); exit; }
- 投稿日:2020-01-22T19:20:03+09:00
AWSで作ったLaravelアプリケーションをXserverにデプロイする
はじめに
タイトル通りですが、LaravelアプリケーションのデプロイをXserverで試してみました。
AWSでアプリケーションは作成したのですが、
XAMPPで作成した方も以下のやり方でできると思います。まとめ
さっそくですがやり方を以下にまとめました。
※XAMPPでアプリケーションを作成した方は⑥以降を参考にしていただければと思います。
①AWSでLaravelアプリケーションを作成。
②作成したアプリケーションのフォルダをダウンロードしてXAMPP環境下(htdocs内)に保存する。
③XAMPPでphpMyAdminを起動。アプリケーション用のDB(DB名:laravel)を作成する。
④.envをXAMPP環境下でDBが使えるようにに編集。
.envDB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD=⑤コマンドプロンプトで同フォルダに移動してテーブル作成
$ cd 作成したアプリのフォルダ $ php artisan migrate//このコマンドでテーブルが作成されます。⑥FTP(FileZillaを使用しました)で転送したいドメイン内のpublic_htmlへ作成したアプリのpublic内のファイルをすべて転送
⑦publicフォルダ以外のファイルをまとめて新しく作ったフォルダに転送(public_htmlと同じ階層下)
⑧public_htmlに転送したindex.phpのrequire先を新しく作ったフォルダ先に変更
index.phprequire __DIR__.'/../laraveltest/vendor/autoload.php';//28行目 ~~ $app = require_once __DIR__.'/../laraveltest/bootstrap/app.php';//38行目⑨XAMPP環境下のphpMyAdminで同アプリ用に作ったテーブルをエクスポート(.sqlファイル)
⑩先ほどエクスポートしたsqlファイルをXserver環境下のphpMyAdminにインポートしてテーブル作成
⑪転送した.envを以下のように編集して保存
.envAPP_URL=http://独自ドメイン/ DB_connection=mysql DB_HOST=自身のサーバ名.xserver.jp DB_PORT=3306 DB_DATABASE=自身のデータベース名 DB_USERNAME=自身のユーザー名 DB_PASSWORD=自身で設定したパスワード⑫対象のドメインへアクセスして問題ないか確認。
参考
参考にさせていただいた記事は以下です。
https://qiita.com/hitotch/items/5c8c2858c883e69cd507最後に
完全に個人的備忘録です。
もしかしたらかなり遠回りな方法でデプロイしているかもしれませんので、
他にも良い方法がありましたら教えていただけますと幸いです。
また、初投稿で書き方もあまりわからなかったのでかなり雑な記事になってしまいました。。。
- 投稿日:2020-01-22T18:02:54+09:00
[baserCMS]ブログのカテゴリーごとに表示を変更する
baserCMSのフォーラムに質問があったので、作成方法を書いてみます。
※書いた後から気づいたのですが、複数ブログで同じカテゴリが存在した場合問題がありますね。
コンテンツテンプレート名を変更してブログ専用のテンプレートとして修正した方が正しいかもです。やり方については後で修正します。カテゴリー名がユニークであれば、category-defaultが表示されるので多分このままでも大丈夫です。Configureを使う方法
baserCMSはCakePHPで出来ているので、Configureにあらかじめ設定を作成しておくと色々なところでデータを使うことができます。
テキスト表示するだけだったら、シンプルかつ追加更新もしやすいかなと思います。bootstrap.phpに設定を書く
ConfigureのCategoryText(任意の名前)に、アーカイブURL【 /ブログ名/archives/category/カテゴリー名 】のカテゴリー名を全て設定で追加しておきます。
使用するテーマ/Config/bootstrap.php<?php /** * bootstrap * * Webページの呼出時、テーマを読み込む前に実行したいプログラムを書きたい場合には、ここに記述します。 */ Configure::write('CategoryText.release','リリースで表示するテキスト'); Configure::write('CategoryText.news','ニュースで表示するテキスト'); Configure::write('CategoryText.campaign','キャンペーンで表示するテキスト'); Configure::write('CategoryText.event','イベントで表示するテキスト');カテゴリー名の取得方法
- \$postの変数が取れる場所 → $post['BlogCategory']['name']
- archives.phpのループ処理外 → \$this->BcBaser->getParams(); ※ $params['pass'][1]の値に入ってる
Blogヘルパーでカテゴリー名を一発で取れないかなぁと思ったんですが、見つかりませんでした。
他に良い方法あれば誰か教えてください。カテゴリー名を元にConfigureの内容を表示する
使用するテーマ/Blog/default/archives.phpなど<?php $params = $this->BcBaser->getParams(); if(Configure::check("CategoryText.".$params['pass'][1])): //カテゴリー名のコンフィグが設定されている時だけ表示する <p><?php echo Configure::read("CategoryText.".$params['pass'][1]);?></p> <?php endif; ?>elementファイルを作成して表示を出し分ける方法
カテゴリーごとに特別なことをしたい場合は、elementを使ってそれぞれのページで色々やるのが良いと思います。
さきほどのカテゴリー名の取得方法を元に作成すれば比較的簡単にできます。archive.phpをコピーして、elementファイルを作成する
共通表示用+オリジナル用のテンプレートをコピーして、必要枚数分作成します。
【使用するテーマ/Blog/default/archives.php】→ 【使用するテーマ/Elements/default/category-カテゴリー名.php】/使用するテーマ/Elements/default/category-default.php
/使用するテーマ/Elements/default/category-release.php
/使用するテーマ/Elements/default/category-campaign.php
のような感じで作成します。archive.phpで出し分けをする
archive.phpをごそっと以下のように書き換えます。
使用するテーマ/Blog/default/archives.php<?php $params = $this->BcBaser->getParams(); //エレメントが存在する場合に読み込む if($this->elementExists('category-'.$params['pass'][1])){ // ※cakephpの関数なので注意 $this->BcBaser->element('category-'.$params['pass'][1]); //オリジナルのカテゴリーテンプレート }else{ $this->BcBaser->element('category-default'); //共通のカテゴリーテンプレート } ?>※ 存在しないカテゴリーのURLを叩いた場合は404になるので、特別な記述は要りません
管理画面でもテキストなどを修正したい場合
自分が作成しているaddConfigのプラグインを使用すると、比較的簡単に画面を作成することができます。
https://github.com/BigFly3/AddConfig
CakePHPユーザーがbaserCMSを開発しやすくするためのカスタムフィールドを作ってみた。
AddConfig を使って固定ページ専用の入力画面みたいなものを作るAddConfig/Config/setting.php$config['AddConfig']['form'][] = [ 'title' => 'ブログカテゴリーのテキスト', //アコーディオン・見出し 'fields' => [ 'categorytext_news_text' => [ //categorytext_カテゴリー名_textの形にする 'required' => true, //必須マークを表示したい場合につける 'label' => 'news', 'parts' => [ //フォームタイプ 'type' => 'textarea', 'rows' => '5', 'cols' => '60', ], 'validate' => [ [ 'rule' => 'notBlank', 'message' => '入力してください' ], ], ], 'categorytext_release_text' => [ //categorytext_カテゴリー名_textの形にする 'required' => true, //必須マークを表示したい場合につける 'label' => 'リリース情報', 'parts' => [ //フォームタイプ 'type' => 'textarea', 'rows' => '5', 'cols' => '60', ], 'validate' => [ [ 'rule' => 'notBlank', 'message' => '入力してください' ], ], ], ] ];設定を書くだけで簡単にフォームを作成できます。
登録のフィールド名を【 categorytext_カテゴリー名_text】の形にするのがポイントです。
このような形にすることで先ほどまでの出し分け処理と同じように、カスタム項目を表示分けすることができます。アーカイブのテンプレート内<?php $params = $this->BcBaser->getParams(); if($this->AddConfig->is('categorytext_'.$params['pass'][1].'_text')): //設定項目があれば表示する?> <p><?php $this->AddConfig->f('categorytext_'.$params['pass'][1].'_text') ;?></p> <?php endif;?>こんな感じでテンプレートに記述を加えれば、カテゴリー専用の表示を作ることができます。
他にもアップロード機能とか色々なフォーム項目があるので、同じ応用で色々なことができると思います。
プラグインを作成しなくても、工夫次第で結構色々出来るので良かったらお試しください。
- 投稿日:2020-01-22T17:45:55+09:00
正規表現なぜ通る?
最近、公共の場でプログラミングをすることにはまっているasuchi0819です。
今日、入力値のチェックをするために正規表現を用いてPHPのpreg_match
関数を使っているときに微妙に躓いたのでそれを備忘録のように書き記しておきます。該当のコード
$text = ''; if(preg_match('/[a-z]+/u', $text)){ echo 'おけみ'; } else { echo 'ダメみ'; }こんなコードがあったとします。
さて、どのような文字列が「おけみ」でしょうか?正解は「aからzまでの文字列を一文字以上含むすべての文字列」です。
はい、言うならばif
の中でpreg_match
は部分一致のように動作してしまいます。じゃあどうすればいいか。
^
と$
を使って完全一致を...してはいけません。
^
と$
を使ってしまうと、完全一致にならない脆弱性が出てきてしまいます。
詳しくは德丸先生の記事をご覧ください。以上を加味すると以下のコードが完成形です。
$text = ''; if(preg_match('/\A[a-z]+\z/u', $text)){ echo 'おけみ'; } else { echo 'ダメみ'; }めでたしめでたし。
もし間違っていたらご指摘いただけるとありがたいです。
- 投稿日:2020-01-22T17:32:35+09:00
Laravel:ブラウザ上の大量のinput要素に自動的に動作確認用のダミーデータを挿入する方法
はじめに
Laravelに限らず開発をする際において、ブラウザ上で動作確認のテストをすることは重要だと思います。(テストコードを書けという意見もありますが、初心者のうちはテストコードなんて書くのが難しかったりしますので、そこは多めにみてください…)
ブラウザ上で動作確認のテストをする際、大量のinput要素に一個一個手打ちでデータを入れていくのはとても手間がかかり、面倒な作業です。例えば、それが100個のinput要素だったりした時には面倒で面倒で仕方ないと思います。そこで、特定のURLにアクセスすると自動的に動作確認用のダミーデータを挿入する方法を書いていこうと思います。
サンプルコードと解説
今回はサンプルなので実際に使う際は自分なりに変えて使ってみてください。
例えば下記のように、こんなマイグレーションファイルがあったとします。<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreatePostsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('posts', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('content1'); $table->string('content2'); $table->string('content3'); $table->string('content4'); $table->string('content5'); $table->string('content6'); $table->string('content7'); $table->string('content8'); $table->string('content9'); $table->string('content10'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('posts'); } }contentを今回は10個用意しました。
さて、次はbladeですが、このように記述します。
@extends('layouts.app') @section('content') <form action="{{ url('/posts/create') }}" method="post"> @csrf @method('POST') <label for="content1">content1</label><br> <input type="text" name="content1" id="content1" value="{{ old('content1', ($post) ? $post->content1 : '') }}"><br> <label for="content2">content2</label><br> <input type="text" name="content2" id="content2" value="{{ old('content2', ($post) ? $post->content2 : '') }}"><br> <label for="content3">content3</label><br> <input type="text" name="content3" id="content3" value="{{ old('content3', ($post) ? $post->content3 : '') }}"><br> <label for="content4">content4</label><br> <input type="text" name="content4" id="content4" value="{{ old('content4', ($post) ? $post->content4 : '') }}"><br> <label for="content5">content5</label><br> <input type="text" name="content5" id="content5" value="{{ old('content5', ($post) ? $post->content5 : '') }}"><br> <label for="content6">content6</label><br> <input type="text" name="content6" id="content6" value="{{ old('content6', ($post) ? $post->content6 : '') }}"><br> <label for="content7">content7</label><br> <input type="text" name="content7" id="content7" value="{{ old('content7', ($post) ? $post->content7 : '') }}"><br> <label for="content8">content8</label><br> <input type="text" name="content8" id="content8" value="{{ old('content8', ($post) ? $post->content8 : '') }}"><br> <label for="content9">content9</label><br> <input type="text" name="content9" id="content9" value="{{ old('content9', ($post) ? $post->content9 : '') }}"><br> <label for="content10">content10</label><br> <input type="text" name="content10" id="content10" value="{{ old('content10', ($post) ? $post->content10 : '') }}"><br> <input type="submit" value="送信"> </form> @endsection続いて、web.phpも書いていきます。
Route::get('/', 'PostsController@index'); Route::get('/posts/create/{type?}', 'PostsController@create'); Route::post('/posts/create', 'PostsController@store');2行目に書いてある
/posts/create/{type?}
が今回重要になってきます。{type?}
はLaravelの任意パラメータというものになります。詳しくはドキュメントを参考にしてください。このように記述し、
アプリ名/posts/create
にアクセスすると、下のようなフォームができていると思います。
フォームができていることを確認したら次はコントローラーを記述していきます。
今回はサンプルで作ったのでバリデーションなどは書いていません。適宜、自分のサービスに合わせてバリデーションを書いてください。<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Post; class PostsController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $posts = Post::all(); return view('posts.index',[ 'posts' => $posts, ]); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create($type = null) { $post = []; if(!is_null($type)){ $post = new Post; $dummy_data = config('form_dummy_data'); if(array_key_exists($type, $dummy_data)){ foreach($dummy_data[$type] as $_key => $_val){ $post->$_key = $_val; } } } return view('posts.create',[ 'post' => $post, ]); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $post = new Post; $post->content1 = $request->content1; $post->content2 = $request->content2; $post->content3 = $request->content3; $post->content4 = $request->content4; $post->content5 = $request->content5; $post->content6 = $request->content6; $post->content7 = $request->content7; $post->content8 = $request->content8; $post->content9 = $request->content9; $post->content10 = $request->content10; $post->save(); return redirect('/'); } }ここで重要になるのはcreateメソッドです。一部抜粋します。
public function create($type = null) { $post = []; if(!is_null($type)){ $post = new Post; $dummy_data = config('form_dummy_data'); if(array_key_exists($type, $dummy_data)){ foreach($dummy_data[$type] as $_key => $_val){ $post->$_key = $_val; } } } return view('posts.create',[ 'post' => $post, ]); }ダミーデータをconfigから呼んでいます。
では、そのダミーデータをconfigディレクトリにファイルを作成して、記述しましょう。
この時に使うファイル名は上で書いたform_dummy_data.php
とします。<?php return [ // バリデーションを通過する入力データ 'post_create_true' => [ 'content1' => 'content1', 'content2' => 'content2', 'content3' => 'content3', 'content4' => 'content4', 'content5' => 'content5', 'content6' => 'content6', 'content7' => 'content7', 'content8' => 'content8', 'content9' => 'content9', 'content10' => 'content10', ], // バリデーションを通過しない入力データ 'post_create_false' => [ 'content1' => 'content1', 'content2' => 'content2', 'content3' => 'content3', 'content4' => 'content4', 'content5' => 'content5', 'content6' => 'content6', 'content7' => 'content7', 'content8' => 'content8', 'content9' => 'content9', 'content10' => 'content10', ] ];trueとfalseで分けているのは、コメントの通り、バリデーションを通すデータと通さないデータに分けているからです。
これでいったん完成なのですが、それでは今度は、
アプリ名/posts/create/post_create_true
か、アプリ名/posts/create/post_create_false
にアクセスしてみてください。フォームに自動的にデータが挿入された状態になっていると思います。
最後に
ブラウザ上で大量のinput要素に自動的にダミーデータを挿入する方法を書きました。
本来ならテストコードを書くべきなのですが、冒頭にも述べた通り、初心者には難しかったり、テストを書く時間がない場合など様々な場面で使えると思います。
ダミーデータも自分のサービスに合わせて作り変えたりして活用してみてください。
- 投稿日:2020-01-22T15:58:40+09:00
PHPでファイルのアップロード時に拡張子の偽装チェックを行う
こんにちは!
クライアントサイドよりアップロードされたファイルに対して、ディレクトリトラバーサル対策等の他にも着目すべき点はありますが、今回は「クライアントサイドのフォームでアップロードしたファイルのバリデーション」のうち、「ファイルの拡張子の偽装チェック」を行うことに着目します!
そもそも拡張子の偽装とは何なのか
mv sample.png sample.jpg
上記例のように内部バイナリのコンバート処理を行うことなく、拡張子を変更することであります。
しかし、Hexとして上記ファイルを検閲すると、
pngファイルと確認することの出来るマジックナンバーがバイナリヘッダーに存在しますね!
では、このファイル名上の拡張子がjpgで内部的にpngであるファイルをクライアントサイドのフォームよりアップロードすると、PHPではどういった挙動になるのか確認してみましょう!
サーバーサイドで拡張子が偽装されたファイルのバリデーションをする
まず前提条件として、下記のフィールドよりアップロードされたファイルを扱うとします。
<input type="file" name="up-file">その際に、
sample.png を sample.jpg にリネームしたファイル
をアップロードします。
すると、echo $_FILES["up-file"]["type"];では、MIME-Typeとして
image/jpegを確認できるはずです。
これは、MIME-Type一覧ページで確認できる通り、
jpgファイル
として認識されていることがわかる証拠です。しかし、
$f_info = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file( $f_info, $_FILES["up-file"]['tmp_name'] ); finfo_close($f_info); //念の為メモリ空間に開いたストリームの破棄を行う echo $mime;として、アップロードされたファイルのバイナリからMIME-Typeを取得すると、
image/pngのように本来は
pngファイル
であることを確認できます。まとめ
$_FILES["識別子"]["type"]で確認出来るMIME-Typeは、ファイル名上の拡張子から推測されるものであることがわかりました!
なので、
- ファイル名上の拡張子から推測できるMIME-Type
- ファイルのバイナリヘッダーから推測出来るMIME-Type
の合致チェックを行って、よりセキュアにファイルのバリデーションを行おこうと思いました!
以下は、サンプルコードです!
$file = $_FILES["識別子"]; $file_mime_type = $file["type"]; $f_info = finfo_open(FILEINFO_MIME_TYPE); $binary_mime_type = finfo_file( $f_info, $file['tmp_name'] ); finfo_close($f_info); $is_same_mime_type = $file_mime_type === $binary_mime_type;ありがとうございました!!!
- 投稿日:2020-01-22T15:58:40+09:00
PHPでファイルのPOST時に拡張子の偽装チェックを行う
こんにちは!
クライアントサイドよりPOSTされたファイルに対して、ディレクトリトラバーサル対策等の他にも着目すべき点はありますが、今回は「クライアントサイドのフォームでPOSTしたファイルのバリデーション」のうち、「ファイルの拡張子の偽装チェック」を行うことに着目します!
そもそも拡張子の偽装とは何なのか
mv sample.png sample.jpg
上記例のように内部バイナリのコンバート処理を行うことなく、拡張子を変更することであります。
しかし、Hexとして上記ファイルを検閲すると、
pngファイルと確認することの出来るマジックナンバーがバイナリヘッダーに存在しますね!
では、このファイル名上の拡張子がjpgで内部的にpngであるファイルをクライアントサイドのフォームよりPOSTすると、PHPではどういった挙動になるのか確認してみましょう!
サーバーサイドで拡張子が偽装されたファイルのバリデーションをする
まず前提条件として、下記のフィールドよりPOSTされたファイルを扱うとします。
<input type="file" name="up-file">その際に、
sample.png を sample.jpg にリネームしたファイル
をPOSTします。
すると、echo $_FILES["up-file"]["type"];では、MIME-Typeとして
image/jpegを確認できるはずです。
これは、MIME-Type一覧ページで確認できる通り、
jpgファイル
として認識されていることがわかる証拠です。しかし、
$f_info = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file( $f_info, $_FILES["up-file"]['tmp_name'] ); finfo_close($f_info); //念の為メモリ空間に開いたストリームの破棄を行う echo $mime;として、POSTされたファイルのバイナリからMIME-Typeを取得すると、
image/pngのように本来は
pngファイル
であることを確認できます。まとめ
$_FILES["識別子"]["type"]で確認出来るMIME-Typeは、ファイル名上の拡張子から推測されるものであることがわかりました!
なので、
- ファイル名上の拡張子から推測できるMIME-Type
- ファイルのバイナリヘッダーから推測出来るMIME-Type
の合致チェックを行って、よりセキュアにファイルのバリデーションを行おこうと思いました!
以下は、サンプルコードです!
$file = $_FILES["識別子"]; $file_mime_type = $file["type"]; $f_info = finfo_open(FILEINFO_MIME_TYPE); $binary_mime_type = finfo_file( $f_info, $file['tmp_name'] ); finfo_close($f_info); $is_same_mime_type = $file_mime_type === $binary_mime_type;ありがとうございました!!!
追記
POSTされたファイルのHexをPHP上で確認する方法。
$binary = file_get_contents( $_FILES["識別子"]["tmp_name"] ); echo bin2hex($binary);bin2hex(正確にはBinary to Hex)でBinaryデータをHexにコンバートします!
上記コード中の$_FILES["識別子"]["tmp_name"]には、POSTされたファイルの一時的(temporary)なパスが格納されているだけなので、バイナリデータとして扱うために
file_get_contents
をしています。あとは、
事ができます!
- 投稿日:2020-01-22T15:58:40+09:00
ファイルのPOST時にPHPで拡張子の偽装チェックを行う
こんにちは!
クライアントサイドよりPOSTされたファイルに対して、ディレクトリトラバーサル対策等の他にも着目すべき点はありますが、今回は「クライアントサイドのフォームでPOSTしたファイルのバリデーション」のうち、「ファイルの拡張子の偽装チェック」を行うことに着目します!
そもそも拡張子の偽装とは何なのか
mv sample.png sample.jpg
上記例のように内部バイナリのコンバート処理を行うことなく、拡張子を変更することであります。
しかし、Hexとして上記ファイルを検閲すると、
pngファイルと確認することの出来るマジックナンバーがバイナリヘッダーに存在しますね!
では、このファイル名上の拡張子がjpgで内部的にpngであるファイルをクライアントサイドのフォームよりPOSTすると、PHPではどういった挙動になるのか確認してみましょう!
サーバーサイドで拡張子が偽装されたファイルのバリデーションをする
まず前提条件として、下記のフィールドよりPOSTされたファイルを扱うとします。
<input type="file" name="up-file">その際に、
sample.png を sample.jpg にリネームしたファイル
をPOSTします。
すると、echo $_FILES["up-file"]["type"];では、MIME-Typeとして
image/jpegを確認できるはずです。
これは、MIME-Type一覧ページで確認できる通り、
jpgファイル
として認識されていることがわかる証拠です。しかし、
$f_info = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file( $f_info, $_FILES["up-file"]['tmp_name'] ); finfo_close($f_info); //念の為メモリ空間に開いたストリームの破棄を行う echo $mime;として、POSTされたファイルのバイナリからMIME-Typeを取得すると、
image/pngのように本来は
pngファイル
であることを確認できます。まとめ
$_FILES["識別子"]["type"]で確認出来るMIME-Typeは、ファイル名上の拡張子から推測されるものであることがわかりました!
なので、
- ファイル名上の拡張子から推測できるMIME-Type
- ファイルのバイナリヘッダーから推測出来るMIME-Type
の合致チェックを行って、よりセキュアにファイルのバリデーションを行おこうと思いました!
以下は、サンプルコードです!
$file = $_FILES["識別子"]; $file_mime_type = $file["type"]; $f_info = finfo_open(FILEINFO_MIME_TYPE); $binary_mime_type = finfo_file( $f_info, $file['tmp_name'] ); finfo_close($f_info); $is_same_mime_type = $file_mime_type === $binary_mime_type;ありがとうございました!!!
追記
POSTされたファイルのHexをPHP上で確認する方法。
$binary = file_get_contents( $_FILES["識別子"]["tmp_name"] ); echo bin2hex($binary);bin2hex(正確にはBinary to Hex)でBinaryデータをHexにコンバートします!
上記コード中の$_FILES["識別子"]["tmp_name"]には、POSTされたファイルの一時的(temporary)なパスが文字列(String型)として格納されているだけなので、バイナリデータとして扱うために
file_get_contents
をしています。あとは、
事ができます!
- 投稿日:2020-01-22T11:44:47+09:00
xamppのPHP、pearのupgradeをしてみる、DBを入れてみる件
はじめに
XAMPP for Windows 7.4.1ではPHPに最初からpearライブラリが入ってるんだけど、DBが入っていないのでインストールしてみた所コケタ件
pearにDBを入れてみる
CD C:\xampp\php\ pear listこれでpearのリストが表示されるんだけどDBは入っていない
とりあえずupgradeしてみるけどpear upgradeNotice: Trying to access array offset on value of type bool in PEAR\REST.php on line 186という文句を言われる。
REST.php on line 186目は厳密にはコメントなんだけど
「If cache is newer than $cachettl seconds, we use the cache!」
で検索してみたところhttps://github.com/pear/pear-core/commit/81b71ecfaeb9eb06debcb5197d18ad4147ad23f3
ここに行き着く。
とりあえず言われたとおりに書き換えてみる
(元の行はコメントで残した方がいいよ)でまたアップグレードしてみると
Fatal error: Cannot use result of built-in function in write context in C:\xampp\php\pear\Archive\Tar.php on line 639今度はTar.phpの639行目がダメって言われる。
こっちはよく「アリガチ」なエラーらしく、すぐに情報が得られるhttps://k3akinori.hatenadiary.org/entry/20180728/1532788736
ということでこれも書き換えてupgrade
いろいろ文句は言われるけどupgradeは成功。
pear install DBDBをインストールしてみたら実行できた。
とりあえずはDBインストール、pear upgradeどっちもできたってことで
- 投稿日:2020-01-22T01:37:16+09:00
LaravelでCSVダウンロード機能を実装する。
CSVダウンロード機能を実装するにあたり、少しはまった部分があったので備忘録です。
流れ
- 出力データを用意
- 出力データをカンマ区切りで配列にセット
- 改行区切りの文字列に変換
- 一時的にCSVファイルを作成
- CSVファイルに書き込み
- ダウンロードレスポンスを返す。
実装
はまったポイント
- 金額など、値にカンマが含まれる文字の扱い -> 値をクオートで囲むことで1つの値として評価される。
- 出力結果が改行されnai -> 改行コードをダブルコートで囲むことで改行コードとして評価される。
- Excelで開いたときに文字化けする -> SJISにエンコーディングする。public function downloadCsv() { // 1. 出力データをを用意 $csvHeader = ["No,", 師匠名, ふりがな]; $csvData = [ [0001, 春風亭助羊, しゅんぷうてい すけよう], [0002, 桂左志郎, かつら さしろう], [0003, 三遊亭遊智, さんゆうてい ゆうち], [0004, 月亭がた枝, つきてい がたし], [0005, 春雨や雷扇, はるさめや らいおう], [0006, 桂喜八宝, かつら きゃぽう], [0007, 翁家八ゑ千代, おきなや やえちよ], [0008, 春雨や雷海, はるさめや らいかい], ]; // 2. 出力データをカンマ区切りで配列にセット $downloadData = []; $downloadData[] = implode(',', $csvHeader); foreach ($csvData as $record) { // 念の為ダブルクオートで囲む foreach ($record as $i => $v) { $record[$i] = '"' . $v . '"'; } $downloadData[] = implode(',', $record); } // 3. 改行区切りの文字列に変換 $downloadData = implode("\r", $downloadData); // 改行コードはダブルクオートで囲む // Excel対応 $downloadData = mb_convert_encoding($downloadData, "SJIS", "UTF-8"); // 4. 一時的にcsvファイルを作成 if (! file_exists(storage_path('csv'))) { $bool = mkdir(storage_path('csv')); if (! $bool) { throw new Exception("ディレクトリを作成できませんでした。", 500); } } $name = 'test.csv'; $pathToFile = storage_path('csv/' . $name); // 5. CSVファイルを作成 if (! file_put_contents($pathToFile, $csvData)) { throw new Exception("ファイルの書き込みに失敗しました。", 500); } // 6. ダウンロードレスポンスを返却 return response()->download($pathToFile, $name)->deleteFileAfterSend(true); }参考
https://gray-code.com/php/download-for-csv-file/
https://teratail.com/questions/2199
https://bayashita.com/p/entry/show/49
https://qiita.com/ikemonn/items/f2bc4f9f834c989084ff
https://www.megasoft.co.jp/support/mifes/faq/miw8faq/faq017.html