Skip to content

Commit 92acae3

Browse files
committed
Make Hashables an array, Move to PHP7, Add timestamps support
1 parent 84ab120 commit 92acae3

File tree

4 files changed

+239
-76
lines changed

4 files changed

+239
-76
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
}
1111
],
1212
"require": {
13-
"php": ">=5.3.0",
13+
"php": ">=7",
1414
"illuminate/support": ">=4.1.0"
1515
},
1616
"autoload": {

readme.md

+14-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ This package allows CSV based seeds.
88

99
### Installation
1010

11-
Require this package in your composer.json and run composer update (or run `composer require flynsarmy/csv-seeder:1.*` directly):
11+
Require this package in your composer.json and run composer update (or run `composer require flynsarmy/csv-seeder:2.*` directly):
1212

13-
"flynsarmy/csv-seeder": "1.0.*"
13+
"flynsarmy/csv-seeder": "2.0.*"
1414

1515

1616
### Usage
@@ -49,13 +49,17 @@ Drop your CSV into */database/seeds/csvs/your_csv.csv* or whatever path you spec
4949

5050
### Configuration
5151

52-
In addition to setting the database table and CSV filename, two other configuration options are available. They can be set in your class constructor:
52+
In addition to setting the database table and CSV filename, the following configuration options are available. They can be set in your class constructor:
5353

5454
- `insert_chunk_size` (int 500) An SQL insert statement will trigger every `insert_chunk_size` number of rows while reading the CSV
5555
- `csv_delimiter` (string ,) The CSV field delimiter.
56-
- `hashable` (string password) Hash the hashable field, useful if you are importing users and need their passwords hashed. Uses `Hash::make()`. Note: This is EXTREMELY SLOW. If you have a lot of rows in your CSV your import will take quite a long time.
56+
- `hashable` (array [password]) List of fields to be hashed before import, useful if you are importing users and need their passwords hashed. Uses `Hash::make()`. Note: This is EXTREMELY SLOW. If you have a lot of rows in your CSV your import will take quite a long time.
5757
- `offset_rows` (int 0) How many rows at the start of the CSV to ignore. Warning: If used, you probably want to set a mapping as your header row in the CSV will be skipped.
5858
- `mapping` (array []) Associative array of csvCol => dbCol. See examples section for details. If not specified, the first row (after offset) of the CSV will be used as the mapping.
59+
- `should_trim` (bool false) Whether to trim the data in each cell of the CSV during import.
60+
- `timestamps` (bool false) Whether or not to add *created_at* and *updated_at* columns on import.
61+
- `created_at` (string current time in ISO 8601 format) Only used if `timestamps` is `true`
62+
- `updated_at` (string current time in ISO 8601 format) Only used if `timestamps` is `true`
5963

6064

6165
### Examples
@@ -113,6 +117,12 @@ Skipping the CSV header row (Note: A mapping is required if this is done):
113117
$this->should_trim = true;
114118
}
115119

120+
### Migration Guide
121+
122+
#### 2.0
123+
124+
- `$seeder->hashable` is now an `array` of columns rather than a single column name. Wrap your old string value in `[]`.
125+
116126
### License
117127

118128
CsvSeeder is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)

src/Flynsarmy/CsvSeeder/CsvSeeder.php

+121-63
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use Log;
55
use DB;
66
use Hash;
7+
use Carbon\Carbon;
78
use Illuminate\Database\Seeder;
89
use Illuminate\Database\Schema;
910

@@ -13,83 +14,89 @@
1314
*/
1415
class CsvSeeder extends Seeder
1516
{
16-
1717
/**
1818
* DB table name
1919
*
2020
* @var string
2121
*/
22-
public $table;
22+
public string $table;
2323

2424
/**
2525
* CSV filename
2626
*
2727
* @var string
2828
*/
29-
public $filename;
29+
public string $filename;
3030

3131
/**
32-
* DB field that to be hashed, most likely a password field.
33-
* If your password has a different name, please overload this
34-
* variable from our seeder class.
35-
*
36-
* @var string
32+
* DB fields to be hashed before import, For example a password field.
3733
*/
38-
39-
public $hashable = 'password';
34+
public array $hashable = ['password'];
4035

4136
/**
4237
* An SQL INSERT query will execute every time this number of rows
4338
* are read from the CSV. Without this, large INSERTS will silently
4439
* fail.
45-
*
46-
* @var int
4740
*/
48-
public $insert_chunk_size = 50;
41+
public int $insert_chunk_size = 50;
4942

5043
/**
5144
* CSV delimiter (defaults to ,)
52-
*
53-
* @var string
5445
*/
55-
public $csv_delimiter = ',';
46+
public string $csv_delimiter = ',';
5647

5748
/**
5849
* Number of rows to skip at the start of the CSV
59-
*
60-
* @var int
6150
*/
62-
public $offset_rows = 0;
51+
public int $offset_rows = 0;
6352

6453
/**
6554
* Can be used to tell the import to trim any leading or trailing white space from the column;
66-
*
67-
* @var bool
6855
*/
69-
public $should_trim = false;
56+
public bool $should_trim = false;
7057

58+
/**
59+
* Add created_at and updated_at to rows
60+
*/
61+
public bool $timestamps = false;
62+
/**
63+
* created_at and updated_at values to be added to each row. Only used if
64+
* $this->timestamps is true
65+
*/
66+
public string $created_at;
67+
public string $updated_at;
7168

7269
/**
7370
* The mapping of CSV to DB column. If not specified manually, the first
7471
* row (after offset_rows) of your CSV will be read as your DB columns.
7572
*
73+
* Mappings take the form of csvColNumber => dbColName.
74+
*
7675
* IE to read the first, third and fourth columns of your CSV only, use:
7776
* array(
7877
* 0 => id,
7978
* 2 => name,
8079
* 3 => description,
8180
* )
82-
*
83-
* @var array
8481
*/
85-
public $mapping = [];
82+
public array $mapping = [];
8683

8784

8885
/**
8986
* Run DB seed
9087
*/
9188
public function run()
9289
{
90+
// Cache created_at and updated_at if we need to
91+
if ($this->timestamps) {
92+
if (!$this->created_at) {
93+
$this->created_at = Carbon::now()->toString();
94+
}
95+
if (!$this->updated_at) {
96+
$this->updated_at = Carbon::now()->toString();
97+
}
98+
}
99+
93100
$this->seedFromCSV($this->filename, $this->csv_delimiter);
94101
}
95102

@@ -99,7 +106,7 @@ public function run()
99106
* @param string $text
100107
* @return string String with BOM stripped
101108
*/
102-
public function stripUtf8Bom($text)
109+
public function stripUtf8Bom(string $text): string
103110
{
104111
$bom = pack('H*', 'EFBBBF');
105112
$text = preg_replace("/^$bom/", '', $text);
@@ -113,7 +120,7 @@ public function stripUtf8Bom($text)
113120
* @param $filename
114121
* @return FALSE|resource
115122
*/
116-
public function openCSV($filename)
123+
public function openCSV(string $filename)
117124
{
118125
if (!file_exists($filename) || !is_readable($filename)) {
119126
Log::error("CSV insert failed: CSV " . $filename . " does not exist or is not readable.");
@@ -136,9 +143,9 @@ public function openCSV($filename)
136143
*
137144
* @param string $filename
138145
* @param string $deliminator
139-
* @return array|bool
146+
* @return array
140147
*/
141-
public function seedFromCSV($filename, $deliminator = ",")
148+
public function seedFromCSV(string $filename, string $deliminator = ","): array
142149
{
143150
$handle = $this->openCSV($filename);
144151

@@ -153,46 +160,42 @@ public function seedFromCSV($filename, $deliminator = ",")
153160
$mapping = $this->mapping ?: [];
154161
$offset = $this->offset_rows;
155162

163+
if ($mapping) {
164+
$this->hashable = $this->removeUnusedHashColumns($mapping);
165+
}
166+
156167
while (($row = fgetcsv($handle, 0, $deliminator)) !== false) {
157168
// Offset the specified number of rows
158169

159-
while ($offset > 0) {
160-
$offset--;
170+
while ($offset-- > 0) {
161171
continue 2;
162172
}
163173

164-
// No mapping specified - grab the first CSV row and use it
174+
// No mapping specified - the first row will be used as the mapping
175+
// ie it's a CSV title row. This row won't be inserted into the DB.
165176
if (!$mapping) {
166-
$mapping = $row;
167-
$mapping[0] = $this->stripUtf8Bom($mapping[0]);
168-
169-
// skip csv columns that don't exist in the database
170-
foreach ($mapping as $index => $fieldname) {
171-
if (!DB::getSchemaBuilder()->hasColumn($this->table, $fieldname)) {
172-
if (isset($mapping[$index])) {
173-
unset($mapping[$index]);
174-
}
175-
}
176-
}
177-
} else {
178-
$row = $this->readRow($row, $mapping);
177+
$mapping = $this->createMappingFromRow($row);
178+
$this->hashable = $this->removeUnusedHashColumns($mapping);
179+
continue;
180+
}
179181

180-
// insert only non-empty rows from the csv file
181-
if (!$row) {
182-
continue;
183-
}
182+
$row = $this->readRow($row, $mapping);
184183

185-
$data[$row_count] = $row;
184+
// insert only non-empty rows from the csv file
185+
if (!$row) {
186+
continue;
187+
}
186188

187-
// Chunk size reached, insert
188-
if (++$row_count == $this->insert_chunk_size) {
189-
$this->insert($data);
190-
$row_count = 0;
191-
// clear the data array explicitly when it was inserted so
192-
// that nothing is left, otherwise a leftover scenario can
193-
// cause duplicate inserts
194-
$data = array();
195-
}
189+
$data[$row_count] = $row;
190+
191+
// Chunk size reached, insert
192+
if (++$row_count == $this->insert_chunk_size) {
193+
$this->insert($data);
194+
$row_count = 0;
195+
// clear the data array explicitly when it was inserted so
196+
// that nothing is left, otherwise a leftover scenario can
197+
// cause duplicate inserts
198+
$data = [];
196199
}
197200
}
198201

@@ -207,14 +210,60 @@ public function seedFromCSV($filename, $deliminator = ",")
207210
return $data;
208211
}
209212

213+
/**
214+
* Creates a CSV->DB column mapping from the given CSV row.
215+
*
216+
* @param array $row
217+
* @return array
218+
*/
219+
public function createMappingFromRow(array $row): array
220+
{
221+
$mapping = $row;
222+
$mapping[0] = $this->stripUtf8Bom($mapping[0]);
223+
224+
// skip csv columns that don't exist in the database
225+
foreach ($mapping as $index => $fieldname) {
226+
if (!DB::getSchemaBuilder()->hasColumn($this->table, $fieldname)) {
227+
if (isset($mapping[$index])) {
228+
unset($mapping[$index]);
229+
}
230+
}
231+
}
232+
233+
return $mapping;
234+
}
235+
236+
/**
237+
* Removes fields from the hashable array that don't exist in our mapping.
238+
*
239+
* This function acts as a performance enhancement - we don't want
240+
* to search for hashable columns on every row imported when we already
241+
* know they don't exist.
242+
*
243+
* @param array $mapping
244+
* @return void
245+
*/
246+
public function removeUnusedHashColumns(array $mapping)
247+
{
248+
$hashables = $this->hashable;
249+
250+
foreach ($hashables as $key => $field) {
251+
if (!in_array($field, $mapping)) {
252+
unset($hashables[$key]);
253+
}
254+
}
255+
256+
return $hashables;
257+
}
258+
210259
/**
211260
* Read a CSV row into a DB insertable array
212261
*
213262
* @param array $row List of CSV columns
214263
* @param array $mapping Array of csvCol => dbCol
215264
* @return array
216265
*/
217-
public function readRow(array $row, array $mapping)
266+
public function readRow(array $row, array $mapping): array
218267
{
219268
$row_values = [];
220269

@@ -226,8 +275,17 @@ public function readRow(array $row, array $mapping)
226275
}
227276
}
228277

229-
if ($this->hashable && isset($row_values[$this->hashable])) {
230-
$row_values[$this->hashable] = Hash::make($row_values[$this->hashable]);
278+
if ($this->hashable) {
279+
foreach ($this->hashable as $columnToHash) {
280+
if (isset($row_values[$columnToHash])) {
281+
$row_values[$columnToHash] = Hash::make($row_values[$columnToHash]);
282+
}
283+
}
284+
}
285+
286+
if ($this->timestamps) {
287+
$row_values['created_at'] = $this->created_at;
288+
$row_values['updated_at'] = $this->updated_at;
231289
}
232290

233291
return $row_values;
@@ -239,7 +297,7 @@ public function readRow(array $row, array $mapping)
239297
* @param array $seedData
240298
* @return bool TRUE on success else FALSE
241299
*/
242-
public function insert(array $seedData)
300+
public function insert(array $seedData): bool
243301
{
244302
try {
245303
DB::table($this->table)->insert($seedData);

0 commit comments

Comments
 (0)