61

I've seen numerous examples on how to take a CSV file and then create an associative array with the headers as the keys.

For example:

Brand,Model,Part,Test
Honda,Civic,123,244
Honda,Civic,135,434
Toyota,Supra,511,664

Where it would create an Array such as Array[$num][$key] where $key would be Brand, Model, Part, Test.

So If I wanted to access the test value "434" I would have to loop every index in the array and then ignore any Brands that were not honda, and any models that were not Civic


What I need to do is access the value most directly, instead of running through a for loop going through each $num index. I want to be able to access the value test "434" with:

Array['Honda']['Civic']['135']

or control a for statement with looping through every model Honda has... something like

foreach $model in Array['Honda']

At the very least I need to be able to go through every model given a known Brand and access all the relative info for each.

Edit:

Just to confirm I was setting this up an example. My actually data has headers like:

brand model part price shipping description footnote

Of which I need to access all the information tied to the part (price, shipping,desc, footnote)

ParoX
  • 5,685
  • 23
  • 81
  • 152
  • 1
    [Example(s) how to do that with `SplFileInfo`](http://stackoverflow.com/a/10181302/367456) – hakre Jan 21 '13 at 10:00

7 Answers7

139

Too many long solutions. I've always found this to be the simplest:

<?php
    /* Map Rows and Loop Through Them */
    $rows   = array_map('str_getcsv', file('file.csv'));
    $header = array_shift($rows);
    $csv    = array();
    foreach($rows as $row) {
        $csv[] = array_combine($header, $row);
    }
?>
Daerik
  • 4,167
  • 2
  • 20
  • 33
  • 4
    marvelous code, it works great with comma separated csv. But what if it is a semicolon separated csv? I don't find the way to do the same with a semicolon separated csv. – Alex Jan 22 '18 at 07:43
  • 4
    @Alex, besides arguing semantics on the acronym CSV being `Comma Separated Values`, you are able to set the delimiter of the [str_getcsv](http://php.net/manual/en/function.str-getcsv.php) function. I hope this helps! – Daerik Jan 22 '18 at 14:30
  • 10
    If you want to pass a parameter (like the delimiter) to str_getcsv, use this: `$rows = array_map(function($row) { return str_getcsv($row, ';'); }, file('file.csv'));` – marcovtwout Feb 09 '18 at 12:47
  • 2
    @marcovtwout, keep in mind that you can pass additional arguments as the third parameter of [array_map()](http://php.net/manual/en/function.array-map.php). – Daerik Feb 09 '18 at 14:24
  • 1
    For CSV in a string: `$rows = array_map('str_getcsv', preg_split('/\r\n|\r|\n/', $string));` – dsharhon Aug 13 '18 at 21:26
  • 6
    this solution is potentially very memory hungry, as it creates an array containing the whole of the CSV file, instead of reading single lines into memory – AngryUbuntuNerd Feb 04 '19 at 11:05
  • I like this, but the lack of error handling at all makes this simpler-appearing than I felt comfortable with. I added additional checking to make sure the file opened correctly. I still think it's simpler than the other solutions here, so thanks! – Brian Stinar Nov 04 '19 at 03:20
  • Why use a foreach when `array_map` works and keeps things consistent? `$csv = array_map(function($row) use ($header) { return array_combine($header, $row); }, $rows);` – Gsinti Feb 08 '20 at 21:26
  • Why doe the first field's header have double quotes around double quotes? "LastName" in CSV becomes ""LastName"" as a header – Dan Apr 03 '20 at 16:05
  • The foreach can be simplified these days: `$csv = array_map(fn($e) => array_combine($header, $e), $rows);` – wivku Oct 08 '21 at 11:45
  • I have found the foreach to run one more time after all data is parsed causing the last entry to simply be false, this causes issues when then iterating and expecting keys to be present. A for loop with count(rows) would probably be better here. – user2280032 Jun 07 '22 at 21:13
73

run over the csv file line by line, and insert to array like:

$array = $fields = array(); $i = 0;
$handle = @fopen("file.csv", "r");
if ($handle) {
    while (($row = fgetcsv($handle, 4096)) !== false) {
        if (empty($fields)) {
            $fields = $row;
            continue;
        }
        foreach ($row as $k=>$value) {
            $array[$i][$fields[$k]] = $value;
        }
        $i++;
    }
    if (!feof($handle)) {
        echo "Error: unexpected fgets() fail\n";
    }
    fclose($handle);
}
Dylan Valade
  • 5,565
  • 6
  • 42
  • 56
Haim Evgi
  • 123,187
  • 45
  • 217
  • 223
  • 16
    If you were to change $array[$i][$k] = $value; to $array[$i][$fields[$k]] = $value; you would get the header as the key. – Craig Hooghiem Jul 18 '13 at 15:51
  • 2
    I was just about to say the same as @CraigHooghiem before I saw his comment. With `$fields[$k]`, the above answer works as expected. – DWils Jun 17 '14 at 23:09
  • If whitespace around header row values is a concern you can clean with `$fields = array_map('trim', $row);` – jerrygarciuh Apr 11 '19 at 16:08
22

To create an associative list array use something like:

$keys = fgetcsv($f);
while (!feof($f)) {
    $array[] = array_combine($keys, fgetcsv($f));
}

And to traverse and filter by specific attributes write a function like:

function find($find) {
    foreach ($array as $row) {
         if (array_intersect_assoc($row, $find) == $find) {
             $result[] = $row;
         }
    }
}

Where you would invoke it with $find = array(Brand=>Honda, Model=>Civic, Part=>135) to filter out the searched models. The other positional array structure seems not very workable, unless you only want to access the "Test" attribute.

mario
  • 144,265
  • 20
  • 237
  • 291
5

Try this simple algorithm:

        $assocData = array();

        if( ($handle = fopen( $importedCSVFile, "r")) !== FALSE) {
            $rowCounter = 0;
            while (($rowData = fgetcsv($handle, 0, ",")) !== FALSE) {
                if( 0 === $rowCounter) {
                    $headerRecord = $rowData;
                } else {
                    foreach( $rowData as $key => $value) {
                        $assocData[ $rowCounter - 1][ $headerRecord[ $key] ] = $value;  
                    }
                }
                $rowCounter++;
            }
            fclose($handle);
        }

        var_dump( $assocData);
Tom Carnell
  • 585
  • 8
  • 14
3

Using fgetcsv() seems the most direct and sensible tool for the job.

csv.csv contents:

Brand,Model,Part,Test
Honda,Civic,123,244
Honda,Civic,135,434
Toyota,Supra,511,664

Code:

$assoc_array = [];
if (($handle = fopen("csv.csv", "r")) !== false) {                 // open for reading
    if (($data = fgetcsv($handle, 1000, ",")) !== false) {         // extract header data
        $keys = $data;                                             // save as keys
    }
    while (($data = fgetcsv($handle, 1000, ",")) !== false) {      // loop remaining rows of data
        $assoc_array[] = array_combine($keys, $data);              // push associative subarrays
    }
    fclose($handle);                                               // close when done
}
echo "<pre>";
    var_export($assoc_array);                                      // print to screen
echo "</pre>";

Output:

array (
  0 => 
  array (
    'Brand' => 'Honda',
    'Model' => 'Civic',
    'Part' => '123',
    'Test' => '244',
  ),
  1 => 
  array (
    'Brand' => 'Honda',
    'Model' => 'Civic',
    'Part' => '135',
    'Test' => '434',
  ),
  2 => 
  array (
    'Brand' => 'Toyota',
    'Model' => 'Supra',
    'Part' => '511',
    'Test' => '664',
  ),
)

Resource: http://php.net/manual/en/function.fgetcsv.php

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
  • Tip: if you use a zero (0) instead of 1000 here: `fgetcsv($handle, 0, ",")`, then there is no character limit per line. Good for if you aren't sure just how many characters might be per line. – Richard Oct 29 '21 at 18:22
2

Here is a solutions that will work by specifying a local file or URL. You can also switch the association on and off. Hopefully this helps.

class CSVData{
    public $file;
    public $data;
    public $fp;
    public $caption=true;
    public function CSVData($file=''){
        if ($file!='') getData($file);
    }
    function getData($file){
        if (strpos($file, 'tp://')!==false){
            copy ($file, '/tmp/csvdata.csv');
            if ($this->fp=fopen('/tmp/csvdata.csv', 'r')!==FALSE){
                $this->readCSV();
                unlink('tmp/csvdata.csv');
            }
        } else {
            $this->fp=fopen($file, 'r');
            $this->readCSV();
        }
        fclose($this->fp);
    }
    private function readCSV(){
        if ($this->caption==true){
            if (($captions=fgetcsv($this->fp, 1000, ","))==false) return false;
        }
        $row=0;
        while (($data = fgetcsv($this->fp, 1000, ",")) !== FALSE) {
            for ($c=0; $c < count($data); $c++) {
                $this->data[$row][$c]=$data[$c];
                if ($this->caption==true){
                    $this->data[$row][$captions[$c]]=$data[$c];
                }
            }
            $row++;
        }
    }
}

Try this usage:

$o=new CSVData();
$o->getData('/home/site/datafile.csv');
$data=$o->data;
print_r($data);
Stranger
  • 10,332
  • 18
  • 78
  • 115
Brad
  • 21
  • 2
1

Here is my solution, similar to others stated but uses a while loop with fgetcsv, and uses a counter and array_combine to set the first row as the keys.

    $rownum = 0;
    while (($row = fgetcsv($openedFile, 1000, ',')) !== FALSE) {
        if ($rownum > 0) {
            $row = array_combine($importarray[0], $row);
        }
        array_push($importarray, $row);
        $rownum++;
    }
    array_shift($importarray);
Kadament
  • 100
  • 1
  • 8