See "Static configuration properties are now immutable, you must use Config API." in the 3.1 change log for details.
5.8 KiB
Import CSV data
Introduction
CSV import can be easily achieved through PHP's built-in fgetcsv()
method,
but this method doesn't know anything about your datamodel. In SilverStripe,
this can be handled through the a specialized CSV importer class that can
be customized to fit your data.
The CsvBulkLoader class
The [api:CsvBulkLoader] class facilitate complex CSV-imports by defining column-mappings and custom converters.
It uses PHP's built-in fgetcsv()
function to process CSV input, and accepts a file handle as an input.
Feature overview:
- Custom column mapping
- Auto-detection of CSV-header rows
- Duplicate detection based on custom criteria
- Automatic generation of relations based on one or more columns in the CSV-Data
- Definition of custom import methods (e.g. for date conversion or combining multiple columns)
- Optional deletion of existing records if they're not present in the CSV-file
- Results grouped by "imported", "updated" and "deleted"
Usage
You can use the CsvBulkLoader without subclassing or other customizations, if the column names
in your CSV file match $db
properties in your dataobject. E.g. a simple import for the
[api:Member]
class could have this data in a file:
FirstName,LastName,Email
Donald,Duck,donald@disney.com
Daisy,Duck,daisy@disney.com
The loader would be triggered through the load()
method:
:::php
$loader = new CsvBulkLoader('Member');
$result = $loader->load('<my-file-path>');
By the way, you can import [api:Member]
and [api:Group]
data through http://localhost/admin/security
interface out of the box.
Import through ModelAdmin
The simplest way to use [api:CsvBulkLoader] is through a [api:ModelAdmin] interface - you get an upload form out of the box.
:::php
<?php
class PlayerAdmin extends ModelAdmin {
private static $managed_models = array(
'Player'
);
private static $model_importers = array(
'Player' => 'PlayerCsvBulkLoader',
);
private static $url_segment = 'players';
}
?>
The new admin interface will be available under http://localhost/admin/players
, the import form is located
below the search form on the left.
Import through a custom controller
You can have more customized logic and interface feedback through a custom controller. Let's create a simple upload form (which is used for MyDataObject
instances). You can access it through http://localhost/MyController/?flush=all
.
:::php
<?php
class MyController extends Controller {
private static $allowed_actions = array('Form');
protected $template = "BlankPage";
public function Link($action = null) {
return Controller::join_links('MyController', $action);
}
public function Form() {
$form = new Form(
$this,
'Form',
new FieldList(
new FileField('CsvFile', false)
),
new FieldList(
new FormAction('doUpload', 'Upload')
),
new RequiredFields()
);
return $form;
}
public function doUpload($data, $form) {
$loader = new CsvBulkLoader('MyDataObject');
$results = $loader->load($_FILES['CsvFile']['tmp_name']);
$messages = array();
if($results->CreatedCount()) $messages[] = sprintf('Imported %d items', $results->CreatedCount());
if($results->UpdatedCount()) $messages[] = sprintf('Updated %d items', $results->UpdatedCount());
if($results->DeletedCount()) $messages[] = sprintf('Deleted %d items', $results->DeletedCount());
if(!$messages) $messages[] = 'No changes';
$form->sessionMessage(implode(', ', $messages), 'good');
return $this->redirectBack();
}
}
Note: This interface is not secured, consider using [api:Permission::check()] to limit the controller to users with certain access rights.
Column mapping and relation import
We're going to use our knowledge from the previous example to import a more sophisticated CSV file.
Sample CSV Content
"SpielerNummer","Name","Geburtsdatum","Gruppe"
11,"John Doe",1982-05-12,"FC Bayern"
12,"Jane Johnson", 1982-05-12,"FC Bayern"
13,"Jimmy Dole",,"Schalke 04"
Datamodel for Player
:::php
<?php
class Player extends DataObject {
private static $db = array(
'PlayerNumber' => 'Int',
'FirstName' => 'Text',
'LastName' => 'Text',
'Birthday' => 'Date',
);
private static $has_one = array(
'Team' => 'FootballTeam'
);
}
?>
Datamodel for FootballTeam:
:::php
<?php
class FootballTeam extends DataObject {
private static $db = array(
'Title' => 'Text',
);
private static $has_many = array(
'Players' => 'Player'
);
}
?>
Sample implementation of a custom loader. Assumes a CSV-file in a certain format (see below).
-
Converts property names
-
Splits a combined "Name" fields from the CSV-data into
FirstName
andLastname
by a custom importer method -
Avoids duplicate imports by a custom
$duplicateChecks
definition -
Creates
Team
relations automatically based on theGruppe
column in the CSV data:::php
'PlayerNumber', 'Name' => '->importFirstAndLastName', 'Geburtsdatum' => 'Birthday', 'Gruppe' => 'Team.Title', ); public $duplicateChecks = array( 'SpielerNummer' => 'PlayerNumber' ); public $relationCallbacks = array( 'Team.Title' => array( 'relationname' => 'Team', 'callback' => 'getTeamByTitle' ) ); public static function importFirstAndLastName(&$obj, $val, $record) { $parts = explode(' ', $val); if(count($parts) != 2) return false; $obj->FirstName = $parts[0]; $obj->LastName = $parts[1]; } public static function getTeamByTitle(&$obj, $val, $record) { return FootballTeam::get()->filter('Title', $val)->First(); ); } } ?>
Related
- [api:CsvParser]
- [api:ModelAdmin]