こんにちは。たきもとです。
先日は私の名刺やブログの見た目が ダサい と連呼してデザインを学ぶ決意をした記事を書きました。
その時の記事はこちら。
デザインはじめました。 | たきもとけんご.com
今回はPHPでcsvファイルの扱いにハマったので、備忘録的な記事を紹介します。
本記事では
-csvファイルの基本的な操作
-csvファイルの一部分だけを変更するための方法
を紹介します。
データの一部を変更するための方針は次の通りです。
- fgetcsv: 一旦csv内の全データを読み込む → 配列に格納(a)
- implode, explode: csvに記録したい新データを(a)と合体
- fputcsv: csvとして出力
動作環境
windows8.1
64bit
xampp v3.2.2
PHP version 5.6.15
Chrome
csvファイル(カンマ区切り)
csvファイルはタブ区切り, カンマ区切りがあります。
今回は後者を前提として考えます。
背景: なぜcsvを部分的に変更したかったのか
私、事業で物販をやってるんです。
現状では商品データ管理をcsvで行っています。
なんだかんだ言ってエクセルに慣れているので、ローカル環境で作業をするにはcsvで管理すると便利なんですよね。
新たな商品を発掘する際、
-過去に検討したデータを考慮
-新たな商品データを追加
等の作業をしたかったんです。
特に、過去のデータを顧みるのは重要です。
「これは扱わない」
と決めた商品を将来的に幾度となく検討するのは時間の無駄です。
これを避けたくて。
一度検討した商品についてはデータ化・リスト化し、今後は検討リストから外す。
リストは検討の度に更新する。
これを実現するには、どうしても過去のデータを参照 & 変更・更新 する必要があったんです。
csv入出力の基本と配列構造の関係
ここからはPHPでcsvを扱うための基本コマンドとその使用例を紹介します。
PHPでは似たような動作をするコマンドが複数用意されています。
ここでは処理速度や可読性を度外視して、現状私が採用している方法を紹介します。
csvデータと配列の関係
ネットでもあまり見かけないので、一度csvデータと配列の関係をまとめておきますね。
今回は例として上図のcsvを扱います。
csvは2次元配列
**配列についてもっと基本的な理解を得たい方は次の記事を参考にして下さい。
[PHP] 私がオススメする配列の作り方 | たきもとけんご.com
csvやxls内のデータは基本的には 2次元配列 になっています。
この2次元配列をPHPに取り込んだ時の配列名を
– $arDB
とします。
これは私が適当につけた名前です。関数ではありません。
念のため。
$arDB[$x][$y];
x: 1次元目 (列方向)
y: 2次元目 (行方向)
x,y はどちらも 0番目 から数え始めます。注意しましょう。
たとえば、
arDB[$x][$y];
がcsvのデータを表しているとします。
そうすると、
arDB[0][0] = no
arDB[0][2] = 100
arDB[2][3] = 1
arDB[4][1] = something_4
となります。
ここで実際にcsvを取り込んだ結果を表示します。
まずはこの配列の感覚を把握しましょう。
array(6) { [0]=> array(4) { [0]=> string(2) "no" [1]=> string(5) "title" [2]=> string(5) "price" [3]=> string(6) "numBuy" } [1]=> array(4) { [0]=> string(1) "1" [1]=> string(11) "something_1" [2]=> string(3) "100" [3]=> string(1) "0" } [2]=> array(4) { [0]=> string(1) "2" [1]=> string(11) "something_2" [2]=> string(3) "300" [3]=> string(1) "1" } [3]=> array(4) { [0]=> string(1) "3" [1]=> string(11) "something_3" [2]=> string(3) "500" [3]=> string(1) "3" } [4]=> array(4) { [0]=> string(1) "4" [1]=> string(11) "something_4" [2]=> string(3) "800" [3]=> string(1) "0" } [5]=> array(4) { [0]=> string(1) "5" [1]=> string(11) "something_5" [2]=> string(4) "1000" [3]=> string(1) "2" } }
csvデータは 1次元ごとに取得される
csvデータは1次元ごとに取り込まれます。
言い換えると、
-最初は1行目
-次は2行目
-次は3行目
・
・
という形で取得されます。
上図を例に考えると、次の順番でcsv内のデータが取得されます。
arDB[0][0] = no
arDB[0][1] = title
arDB[0][2] = price
arDB[0][3] = numBuy
arDB[1][0] = 1
arDB[1][1] = something_1
arDB[1][2] = 100
arDB[1][3] = 0
・
・
・
read
使う関数: fopen, fgetcsv
fopneでcsvファイルをオープン。
fgetcsv でポインタの位置を指定。
fopenのオプションは公式マニュアルを参考にしましょう。
正直、このオプションについてはかなり分かりにくいですが・・・。
fopen | PHPマニュアル
fgetcsv | PHPマニュアル
$dirPath = 'C:\Users\(自分のユーザ名)\Desktop\\'; $dbName = '160418_data';// 今回はcsvファイル名をこうしています。 $extension = '.csv'; $readFile = $dirPath.$dbName.$extension;//読み込みたいcsvファイルの在処 & ファイル名 $rHandle = fopen($readFile,"r"); while ($arList = fgetcsv($rHandle)) {//一度DBの全データを取得 $arDB[] = $arList; } fclose($rHandle);
while文で1行ずつデータを取得してゆきます。
write
使う関数: fopen, fputcsv
fputcsv();
fopenはreadのときと役割は一緒です。オプションだけ注意しましょう。
fputcsvはcsvファイルを作成するときのコマンドです。
$wHandle = fopen($readFile,"w");// readしたDBを書き替える foreach ($wData as $val) {// wData=書き込みたいデータ fputcsv($wHandle,$val); } fclose($wHandle);
csvを部分的に変更して更新する方法
さて、ここまででcsvファイルの基本動作はマスターしたかと思います。
次はcsvファイルの部分的な変更方法について紹介します。
基本的な先述した通りですが、再度紹介します。
- fgetcsv: 一旦csv内の全データを読み込む → 配列に格納(a)
- implode, explode: csvに記録したい新データを(a)と合体
- fputcsv: csvとして出力
一旦csv内の全データを読み込む
ここは先述した通りです。
今回はデスクトップ内のファイルを指定しています。
この点はご自分の環境に合わせてカスタマイズして下さい。
csvに記録したいデータ(配列)を基のデータと合体
使う関数: implode, explode
implode | PHP公式マニュアル
explode | PHPマニュアル
csvファイルをreadして分かったように、行、列に格納したいデータを次のようにする必要があります。
[0]=> array(4) { [0]=> string(2) "no" [1]=> string(5) "title" [2]=> string(5) "price" [3]=> string(6) "numBuy" } [1]=> array(4) { [0]=> string(1) "1" [1]=> string(11) "something_1" [2]=> string(3) "100" [3]=> string(1) "0" }
・
・
そこで、
-元のデータ
-書き込みたいデータ
を上手く分割(implode), 結合(explode)して上図のような配列構造にすることが目標です。
今回の例ではexplodeのみで解決できたので、こちらだけを使用します。
今回はpriceを変更してみる例で操作を確認します。
基データ($arDB)の価格を次のように変更してみましょう。
新たに取得した価格は次のような配列に格納される必要があります。
$arDB[1][2] = 200
$arDB[2][2] = 600
$arDB[3][2] = 1000
$arDB[4][2] = 1600
$arDB[5][2] = 2000
これを目指して配列を組み立ててゆくと次のような記述になります。
/** * 新たに取得した価格 */ $newPrice = [ '200',//[0] '600',//[1] '1000',//[2] '1600',//[3] '2000',//[4] ]; /** * csvに格納する配列($wData)を作成 * * csvがカンマ区切りなので、 ','で結合する */ //wData作成 $cntKeyNewPrice = 0; //$newPrice のキーは 0 から始まるから $numWriteData = count($arDB); for ($i=0; $i < $numWriteData; $i++) { if ($i == 0) { $wData[$i] = $arDB[$i];//1行目@csv基データをそのまま記録 } else { $tmpWdata = $arDB[$i][0].','.$arDB[$i][1].','.$newPrice[$cntKeyNewPrice].','.$arDB[$i][3]; $wData[$i] = explode(',', $tmpWdata); $cntKeyNewPrice += 1;// カウントアップ } }
ポイントは次の通りです。
- csvはカンマ区切りなので, “,”で結合する
- $newPriceのキーは 0 から始まる事に注意
- csvファイル 1行目 は列名であること
上記3番目が理由で,$newPriceのキーを $i とは別にカウントアップする必要がありました。
csvとして出力
ここまでできたら、後は先述した様にcsv出力するだけです。
出来上がったコード
<?php /** * DBアクセス */ $dirPath = 'C:\Users\(自分のユーザ名)\Desktop\\'; $dbName = '160418_data'; $extension = '.csv'; $readFile = $dirPath.$dbName.$extension; $rHandle = fopen($readFile,"r"); //read Handle while ($arList = fgetcsv($rHandle)) {//一度DBの全データを取得 $arDB[] = $arList; } fclose($rHandle); /** * 新たに取得した価格 */ $newPrice = [ '200',//[0] '600',//[1] '1000',//[2] '1600',//[3] '2000',//[4] ]; /** * csvに格納する配列($wData)を作成 * * csvがカンマ区切りなので、 ','で結合する */ //wData作成 $cntKeyNewPrice = 0; //$newPrice のキーは 0 から始まるから $numWriteData = count($arDB); for ($i=0; $i < $numWriteData; $i++) { if ($i == 0) { $wData[$i] = $arDB[$i];//1行目@csv基データをそのまま記録 } else { $tmpWdata = $arDB[$i][0].','.$arDB[$i][1].','.$newPrice[$cntKeyNewPrice].','.$arDB[$i][3]; $wData[$i] = explode(',', $tmpWdata); $cntKeyNewPrice += 1;// カウントアップ } } /** * csv出力 */ $wHandle = fopen($readFile,"w");// 更新したいcsvファイルを呼び出す foreach ($wData as $val) { fputcsv($wHandle,$val); } fclose($wHandle);
まとめ
今回の方法は力技で解決した雰囲気満々です。
もっとスマートな記述方法があるかもしれませんが、私は本記事のようにしました。
DB、たとえばMySQLなんかはレコードやフィールドの一部を更新するためのコマンドが用意されていますよね。
この点に比べたらPHPでcsvを扱うのはやや不便です。
私はちょっとした作業時はcsvにデータを溜める事が多いです。
理由は、扱うデータをエクセルで処理することが度々あるのでcsvだと管理が楽だからです。
今はcsvをメインで扱っていますが、WEBサービスやアプリをリリースするときはcsvではなくDBをメインで使うと思います。
とはいえ、今回の手法は頻繁に利用します。
頻繁に使うのに、その度に躓きます。
だから記事にしました。
私ももっと修行を積まなければ。
本記事が多くの方のお役に立てば幸いです。
今回は以上です。