Last active
October 16, 2024 09:17
-
-
Save rc1021/4a80b4ff9c8cec51bebee2a530c99eaf to your computer and use it in GitHub Desktop.
Revisions
-
rc1021 revised this gist
Oct 16, 2024 . 1 changed file with 6 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -11,12 +11,12 @@ $csv = new CsvFileEditor('file.csv'); ## 新增資料在最後一行 ``` $csv->appendData(['Username', 'Cellphone']) ->appendData(['User1', 'Phone 0001']) ->appendData(['User2', 'Phone 0002']) ->appendData(['User3', 'Phone 0003', 'data1', 'data2']) ->appendData(['User4']) ->appendData(['User5', '']); ``` ## 更新指定行數的資料 -
rc1021 revised this gist
Oct 16, 2024 . 2 changed files with 34 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -208,4 +208,31 @@ public function updateAtLine($data, $line = 1) } } } /** * 將檔案輸出陣列 * * @return array */ public function toArray() { if($filePath = $this->exists()) { if ($fileContents = file($filePath)) { // 將字串轉換為類似文件的資源 $handle = fopen('php://temp', 'r+'); foreach($fileContents as $csvString) { fwrite($handle, $csvString); } // 將文件指標指回到文件開頭 rewind($handle); $array = []; while (($data = fgetcsv($handle)) !== false) { array_push($array, $data); } fclose($handle); return $array; } } return []; } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -81,4 +81,11 @@ $data = $csv->findPrefixFirst('User3', $findLineNumber, true); * 4 => "data3", * ] */ ``` ## 將檔案輸出陣列 ``` $csv->toArray(); // [[], ...] ``` -
rc1021 created this gist
Oct 16, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,211 @@ <?php namespace App\Commons; use Illuminate\Support\Facades\Storage; class CsvFileEditor { /** @var string csv檔案路徑 */ public $filepath; function __construct($filepath, $disk = null) { // 確定 $filepath 是否為完整路徑 if (!realpath($filepath)) $filepath = Storage::disk($disk)->path('csv_file_editor/' . ltrim($filepath, '/')); $this->filepath = $filepath; } /** * 將陣列轉為 csv 字串 * * @param mixed $data * @return string */ private function convertArrayToCsvString(array $data) { $fp = fopen('php://temp', 'r+'); fputcsv($fp, array_values($data)); rewind($fp); $csvData = stream_get_contents($fp); fclose($fp); return $csvData; } /** * 將 csv 字串轉為陣列 * * @param mixed $csvString * @return array */ private function convertCsvStringToArray($csvString) { // 將字串轉換為類似文件的資源 $handle = fopen('php://memory', 'r+'); fwrite($handle, $csvString); // 將文件指標指回到文件開頭 rewind($handle); // 取得第一行 csv $csvData = fgetcsv($handle); fclose($handle); return $csvData; } /** * combineArraysWithPadding * * @param mixed $keys * @param mixed $values * @return array */ private function combineArrayWithPaddingOrTrimming($keys, $values) { $numKeys = count($keys); $numValues = count($values); // 如果 keys 的數量少於 values,則補足 if ($numKeys < $numValues) { // 使用一個範圍來生成補足的鍵名 $keys = array_merge($keys, range($numKeys, $numValues - 1)); } // 只保留前面的鍵名 $keys = array_slice($keys, 0, $numValues); // 使用 array_combine 來合併 return array_combine($keys, $values); } /** * 檔案是否存在 * * @return string|bool */ public function exists() { return realpath($this->filepath) ? $this->filepath : false; } /** * 檔案路徑 * * @return string */ public function filePath() { return $this->filepath; } /** * 取得指定行數的資料 * * @param mixed $line 指定行數,預設第1行 * @param mixed $withColumnName 是否加入欄位名稱(第一行) * @return array */ public function fetchAtLine($line = 1, $withColumnName = false) { if($filePath = $this->exists()) { if ($handle = fopen($filePath, 'r')) { // 標題欄 $header = []; $currentLine = 0; while (($data = fgetcsv($handle)) !== false) { if (empty($header)) $header = $data; if (++$currentLine === $line) { fclose($handle); if ($withColumnName) return $this->combineArrayWithPaddingOrTrimming($header, $data); return $data; } } fclose($handle); } } return []; } /** * 找到第一個 $prefix 文字開頭的行內容 * * @param mixed $prefix * @param mixed $findAtLine 在第 n 行找到的資料,找不到資料時值為最後一行 * @param mixed $withColumnName 是否加入欄位名稱(第一行) * @return array */ public function findPrefixFirst ($prefix, &$findAtLine = 0, $withColumnName = false) { if($filePath = $this->exists()) { if ($handle = fopen($filePath, 'r')) { // 標題欄 $header = []; // 逐行讀取 while (($line = fgets($handle)) !== false) { if (empty($header)) $header = $this->convertCsvStringToArray($line); ++$findAtLine; // 檢查該行是否以指定字串開頭 if (strpos($line, $prefix) === 0) { fclose($handle); if ($withColumnName) return $this->combineArrayWithPaddingOrTrimming($header, $this->convertCsvStringToArray($line)); return $this->convertCsvStringToArray($line); } } fclose($handle); } } return []; } /** * 將 $data 寫入最後一行 * * @param mixed $data * @return self */ public function appendData(array $data) { $dirPath = dirname($this->filepath); if (!is_dir($dirPath)) { mkdir($dirPath, 0755, true); // 創建目錄及其父目錄 } $csvData = $this->convertArrayToCsvString($data); file_put_contents($this->filepath, $csvData, FILE_APPEND | LOCK_EX); return $this; } /** * 更新指定行數的資料 * * @param mixed $data * @param mixed $line * @return void */ public function updateAtLine($data, $line = 1) { if($filePath = $this->exists()) { // 檢查是否存在 sed 命令 $hasSed = shell_exec('command -v sed'); if ($hasSed) { // 如果有 sed,使用 sed -i 來修改第一行 $escapedLine = escapeshellarg($this->convertArrayToCsvString($data)); // 避免命令注入風險 exec("sed -i $line . 's/.*/$escapedLine/' $filePath"); } // 否則使用 PHP 方法來修改第一行 else { // 打開文件,讀取內容 $fileContents = file($filePath); // 行數存在才更新 if (!empty($fileContents) && isset($fileContents[$line - 1])) { $fileContents[$line - 1] = $this->convertArrayToCsvString($data); file_put_contents($filePath, implode("", $fileContents), LOCK_EX); } } } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,84 @@ # 使用方式 ## 建立物件 ``` use App\Commons\CsvFileEditor; $csv = new CsvFileEditor('file.csv'); // file path: /var/www/storage/app/csv_file_editor/test.csv ``` ## 新增資料在最後一行 ``` $csv->appendData(['Username', 'Cellphone']); $csv->appendData(['User1', 'Phone 0001']); $csv->appendData(['User2', 'Phone 0002']); $csv->appendData(['User3', 'Phone 0003', 'data1', 'data2']); $csv->appendData(['User4']); $csv->appendData(['User5', '']); ``` ## 更新指定行數的資料 ``` // 更新第 4 行資料 $csv->updateAtLine(['User3-1', 'Phone 0003', 'data1', 'data2', 'data3'], 4); ``` ## 取得指定行數的資料 ``` $data = $csv->fetchAtLine(4); /** * $data: [ * "User3-1", * "Phone 0003", * "data1", * "data2", * "data3", * ] */ $data = $csv->fetchAtLine(4, true); /** * $data: [ * "Username" => "User3-1", * "Cellphone" => ""Phone 0003", * 2 => "data1", * 3 => "data2", * 4 => "data3", * ] */ ``` ## 取得第一行以指定文字開頭的資料 ``` $data = $csv->findPrefixFirst('User3', $findLineNumber); /** * $findLineNumber; // 4 * * $data: [ * "User3-1", * "Phone 0003", * "data1", * "data2", * "data3", * ] */ $data = $csv->findPrefixFirst('User3', $findLineNumber, true); /** * $findLineNumber; // 4 * * $data: [ * "Username" => "User3-1", * "Cellphone" => ""Phone 0003", * 2 => "data1", * 3 => "data2", * 4 => "data3", * ] */ ```