Using the API to import CSV data with PHP
Demo application that uses a CSV oparse and the Kimai API to import local data
This example application uses the API to import a list of customers, projects and activities from a CSV file.
First create the following composer.json file and run composer install afterward.
{
"name": "kimai/api_demo",
"require": {
"guzzlehttp/guzzle": "^7.3",
"symfony/console": "^5.3",
"league/csv": "^9.7",
"ext-json": "*"
}
}
Now create a file with the name importer and adjust the KIMAI_API_* constants to your installation:
#!/usr/bin/env php
<?php
require __DIR__.'/vendor/autoload.php';
define('KIMAI_API_URL', 'https://demo.kimai.org/api/');
define('KIMAI_API_TOKEN', 'api_kitten_super');
use GuzzleHttp\Client;
use League\Csv\Reader;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SingleCommandApplication;
use Symfony\Component\Console\Style\SymfonyStyle;
(new SingleCommandApplication())
->setName('Kimai - Simple activity importer')
->setVersion('0.3')
->addArgument('file', InputArgument::REQUIRED, 'The CSV file to import')
->addOption('delimiter', null, InputOption::VALUE_REQUIRED, 'The delimiter to use (by default comma ",")', ',')
->setCode(function (InputInterface $input, OutputInterface $output) {
$io = new SymfonyStyle($input, $output);
$file = $input->getArgument('file');
if (!file_exists($file)) {
$io->error('Cannot find file: ' . $file);
return 1;
}
if (!is_readable($file)) {
$io->error('Cannot read file: ' . $file);
return 2;
}
$delimiter = $input->getOption('delimiter');
if (empty($delimiter)) {
$io->error('Cannot use empty delimiter');
return 3;
}
$csv = Reader::createFromPath($file, 'r');
$csv->setDelimiter($delimiter);
$csv->setHeaderOffset(0);
$client = new Client([
'base_uri' => KIMAI_API_URL,
'verify' => false,
'headers' => ['Authorization' => 'Bearer ' . KIMAI_API_TOKEN]
]);
$customers = 0;
$projects = 0;
$activities = 0;
$customerIds = [];
$projectIds = [];
$activityIds = [];
$doPost = function (Client $client, string $endpoint, array $data) {
$response = $client->post($endpoint, ['json' => $data]);
return json_decode($response->getBody(), true);
};
$records = iterator_to_array($csv->getRecords());
$progressBar = new ProgressBar($output, count($records));
foreach($records as $record) {
if (!array_key_exists('Customer', $record)) {
$io->error('Cannot import row, missing field "Customer" in row: ' . implode($delimiter, $record));
return 4;
}
if (!array_key_exists('Project', $record)) {
$io->error('Cannot import row, missing field "Project" in row: ' . implode($delimiter, $record));
return 5;
}
if (!array_key_exists('Activity', $record)) {
$io->error('Cannot import row, missing field "Activity" in row: ' . implode($delimiter, $record));
return 6;
}
$customerName = trim($record['Customer']);
$projectName = trim($record['Project']);
$activityName = trim($record['Activity']);
if (!array_key_exists($customerName, $customerIds)) {
$customer = $doPost($client, 'customers', ['name' => mb_substr($customerName, 0, 150), 'country' => 'IT', 'currency' => 'EUR', 'timezone' => 'Europe/Rome', 'visible' => true]);
$customerIds[$customerName] = $customer['id'];
$customers++;
}
$customerId = (int) $customerIds[$customerName];
if (!array_key_exists($customerId, $projectIds)) {
$projectIds[$customerId] = [];
}
if (!array_key_exists($projectName, $projectIds[$customerId])) {
$project = $doPost($client, 'projects', ['name' => mb_substr($projectName, 0, 150), 'customer' => $customerId, 'visible' => true]);
$projectIds[$customerId][$projectName] = $project['id'];
$projects++;
}
$projectId = (int) $projectIds[$customerId][$projectName];
if (!array_key_exists($projectId, $activityIds)) {
$activityIds[$projectId] = [];
}
if (!array_key_exists($activityName, $activityIds[$projectId])) {
$activity = $doPost($client, 'activities', ['name' => mb_substr($activityName, 0, 150), 'project' => $projectId, 'visible' => true]);
$activityIds[$projectId][$activityName] = $activity['id'];
$activities++;
}
$progressBar->advance();
}
$progressBar->finish();
$io->success('Created ' . $customers . ' customers');
$io->success('Created ' . $projects . ' projects');
$io->success('Created ' . $activities . ' activities');
})
->run();
Set execute permission chmod +x importer and start it by passing the filename for your CSV ./importer ~/kimai-import.csv,
which should be formatted like this:
Customer,Project,Activity
DEMO,FOO,A long activity title
DEMO,FOO,Demand management
DEMO,BAR,Software as a service
DEMO,BAR,Administration
DEMO,BAR,Video conference
DEMO,BAR,Sales talk
DEMO,Hello world,I see you
DEMO,Hello world,Testing the API
Related articles
- Start using the Kimai API – Access your time-tracking data with the Kimai JSON API
- Using the API to import CSV data with PHP – Demo application that uses a CSV oparse and the Kimai API to import local data