Odyssey
CsvToArray.php
1 <?php
2 /**
3  * @copyright HomeCu 05/2019
4  *
5  * A wrapper class for str_getcsv() to parse a CSV file into an array.
6  * Leaves off escape param, can be added if needed . . . .
7  * ***ASSUMES*** (and really should be) first row of CSV are field names. It is shifted
8  * off the stack and used as field names for associative array as below.
9  *
10  * Usage:
11  * $obj = new CsvToArray([path to dummie data CSV file], [optional string delimiter], [optional string enclosure]);
12  * $obj->ParseData();
13  *
14  * "Enclosure" is better known as "qualifier" as in quote-qualified fields.
15  *
16  * Returns array [
17  * 'errors' => [],
18  * 'data' => [
19  * [int row index] => [
20  * [string field] => [mixed value],
21  * ....
22  * ]
23  * ];
24  *
25  */
27 {
28  /** @var string|null for str_getcsv(), defaults to ',' */
29  protected $delimiter = null;
30 
31  /** @var string|null for str_getcsv(), also known as qualifier as in "quote qualified fields" */
32  protected $enclosure = null;
33 
34  /** @var array data returned as documented above */
35  protected $response = [];
36 
37  /** @var int keeps track of line seek internally */
38  protected $line_num = 0;
39 
40  /** @var SplFileObject, object created WITH the injected CSV path */
41  protected $SplFileObj = null;
42 
43  /**
44  * @var array on construction create the return associative array. On subsequent lines,
45  * we use array_combine() to return an associative array containing the CSV data.
46  */
47  protected $array_template = [];
48 
49  /**
50  * Constructor, initialize response and set path to CSV. Set delimiter and
51  * enclosure first, setArrayTemplate() uses them.
52  * @param string $csv_path
53  * @param string $delimiter
54  * @param string $enclosure
55  * @return void
56  */
57  public function __construct($csv_path = '', $delimiter = ',', $enclosure = null) {
58 
59  $this
60  ->InitResponseArray()
61  ->Set('delimiter', $delimiter)
62  ->Set('enclosure', $enclosure)
63  ->SetFileObject($csv_path)
64  ->SetArrayTemplate();
65  }
66 
67  /**
68  * Get the associative array of $this->array_template mapped to CSV data rows.
69  * @return array|false
70  */
71  public function ParseData() {
72 
73  $this->line_num++;
74  $this->SplFileObj->seek($this->line_num);
75 
76  if ($this->SplFileObj->eof()) {
77  $this->CloseFile();
78  return false;
79  }
80 
81  return $this
82  ->CombineCsvArray($this->SplFileObj->current())
83  ->ReturnResponse();
84  }
85 
86 
87  /**
88  * Check that the SplFileObject is still an object (not null)
89  * @return bool
90  */
91  public function IsLinesToProcess() {
92 
93  return ! is_null($this->SplFileObj);
94  }
95 
96  /**
97  * Initialize the response
98  * @return $this
99  */
100  protected function InitResponseArray() {
101 
102  $this->response = [
103  'errors' => [],
104  'data' => []
105  ];
106 
107  return $this;
108  }
109 
110  /**
111  * A typical setter, sets internal properties (delimiter, enclosure)
112  * Usually setters are public, don't think we'll need it externally
113  * @param string $prop property identifier
114  * @param string $value property to set
115  * @return $this
116  */
117  protected function Set($prop, $value) {
118 
119  if (! $value || empty($value)) {
120  return $this;
121  }
122 
123  $this->{$prop} = $value;
124 
125  return $this;
126  }
127 
128  /**
129  * Instantiate the native SplFileObject. We could call it directly, setting
130  * it as a property makes it a little more visible.
131  * @param string $file
132  * @return $this
133  */
134  protected function SetFileObject($file) {
135 
136  $this->SplFileObj = new SplFileObject($file);
137 
138  return $this;
139  }
140 
141  /**
142  * Initialize $this->array_template from the first row in the CSV.
143  * @return $this
144  */
145  protected function SetArrayTemplate() {
146 
147  $this->SplFileObj->seek(0);
148  $this->array_template = str_getcsv($this->SplFileObj->current(), $this->delimiter, $this->enclosure);
149 
150  return $this;
151  }
152 
153  /**
154  * Leverage str_getcsv()to parse the CSV file.
155  * @param string $line
156  * @return $this
157  */
158  protected function CombineCsvArray($line = '') {
159 
160  if ($this->IsErrors()) {
161  return $this;
162  }
163 
164  $data = str_getcsv($line, $this->delimiter, $this->enclosure);
165 
166  if (! $this->DataMatchesTemplateLength($data)) {
167 
168  $this->response['errors'][] = "
169  The data length is incorrect, please check the CSV file.
170  The columns in the header MUST match the columns in the data rows." . PHP_EOL;
171  return $this;
172  }
173 
174  $this->response['data'] = $this->CreateAssociativeArrays($data);
175 
176  return $this;
177  }
178 
179  /**
180  * Validate we can do array_combine
181  * @param array $data
182  * @return bool
183  */
184  protected function DataMatchesTemplateLength($data) {
185 
186  return count($data) == count($this->array_template);
187  }
188 
189  /**
190  * Map the data to the first row of fields so we have an associative array.
191  * @param array $data
192  * @return array
193  */
194  protected function CreateAssociativeArrays($data) {
195 
196  return array_combine($this->array_template, $data);
197  }
198 
199  /**
200  * Close filehandles by setting SplFileObject to null. Public so if the
201  * requested number of records is less than file length we can close it.
202  * @return $this
203  */
204  public function CloseFile() {
205 
206  if ($this->SplFileObj) {
207  $this->SplFileObj = null;
208  }
209 
210  return $this;
211  }
212 
213  /**
214  * Return a response, this approach allows chaining of methods above it.
215  * @return array
216  */
217  protected function ReturnResponse() {
218 
219  return $this->response;
220  }
221 
222  /**
223  * Got errors?
224  * @return bool
225  */
226  protected function IsErrors() {
227 
228  return count($this->response['errors']) > 0;
229  }
230 
231 }
DataMatchesTemplateLength($data)
Definition: CsvToArray.php:184
SetArrayTemplate()
Definition: CsvToArray.php:145
Set($prop, $value)
Definition: CsvToArray.php:117
CombineCsvArray($line='')
Definition: CsvToArray.php:158
CreateAssociativeArrays($data)
Definition: CsvToArray.php:194
SetFileObject($file)
Definition: CsvToArray.php:134
InitResponseArray()
Definition: CsvToArray.php:100
IsLinesToProcess()
Definition: CsvToArray.php:91
__construct($csv_path='', $delimiter=',', $enclosure=null)
Definition: CsvToArray.php:57