241 lines
6.7 KiB
PHP
241 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace EesyPHP\Export;
|
|
|
|
use EesyPHP\Date;
|
|
use EesyPHP\Log;
|
|
|
|
use function EesyPHP\implode_with_keys;
|
|
|
|
/**
|
|
* CSV export
|
|
* @property-read string $separator
|
|
* @property-read string $enclosure
|
|
* @property-read string $escape
|
|
* @property-read string $eol
|
|
* @property-read int|null $max_length
|
|
* @property-read string $date_format
|
|
* @property-read bool|string $trim
|
|
*/
|
|
class CSV extends Generic {
|
|
|
|
/**
|
|
* Array of fields in export. Could be an associative array to specify custom exporting
|
|
* parameters:
|
|
* [
|
|
* 'name',
|
|
* 'name' => 'label',
|
|
* 'name' => [
|
|
* 'label' => 'Name',
|
|
* 'to_string' => [callable],
|
|
* 'from_string' => [callable],
|
|
* ],
|
|
* ].
|
|
* @var array<string,string>|array<string>
|
|
*/
|
|
protected $fields;
|
|
|
|
/**
|
|
* Array of export parameters default value
|
|
* @var array<string,mixed>
|
|
*/
|
|
protected static $default_parameters = [
|
|
"separator" => ",", // The fieds separator character
|
|
"enclosure" => "\"", // The enclosure character
|
|
"escape" => "\\", // The enclosure character
|
|
"eol" => "\n", // The end-of-line character. Note: only used in PHP >= 8.1.0.
|
|
// Line max length, see fgetcsv()
|
|
"max_length" => PHP_VERSION_ID >= 80000?null:99999,
|
|
// DateTime object exporting format
|
|
"date_format" => 'Y/m/d H:i:s',
|
|
// Specify if values loaded from CSV file have to be trim. Could be a boolean or a string of the
|
|
// stripped characters.
|
|
"trim" => true,
|
|
];
|
|
|
|
/**
|
|
* Compute fields mapping info
|
|
* @return array<string,mixed> Fields mapping info
|
|
*/
|
|
protected function _fields_mapping() {
|
|
$map = [];
|
|
foreach(parent :: _fields_mapping() as $key => $info) {
|
|
$map[$key] = [
|
|
'label' => $info['label'],
|
|
'to_string' => (
|
|
array_key_exists('to_string', $info)?
|
|
$info['to_string']:[$this, 'to_string']
|
|
),
|
|
'from_string' => (
|
|
array_key_exists('from_string', $info)?
|
|
$info['from_string']:[$this, 'from_string']
|
|
),
|
|
];
|
|
}
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Convert value to string as expected in the export
|
|
* @param mixed $value The value to export
|
|
* @return string The value as string
|
|
*/
|
|
protected function to_string($value) {
|
|
if (is_null($value))
|
|
return '';
|
|
if (is_a($value, "\DateTime"))
|
|
return Date :: format($value, $this -> date_format);
|
|
return strval($value);
|
|
}
|
|
|
|
/**
|
|
* Convert value from string
|
|
* @param string $value The value to convert
|
|
* @return mixed The converted value
|
|
*/
|
|
protected function from_string($value) {
|
|
if ($this -> trim) {
|
|
if (is_string($this -> trim))
|
|
$value = trim($value, $this -> trim);
|
|
else
|
|
$value = trim($value);
|
|
}
|
|
return empty($value)?null:$value;
|
|
}
|
|
|
|
/**
|
|
* fputcsv wrapper in context of this export
|
|
* @param resource $fd The file pointer of the export
|
|
* @param array<string> $fields The field of the row to export
|
|
* @return boolean
|
|
*/
|
|
public function fputcsv($fd, $fields) {
|
|
$args = [$fd, $fields, $this -> separator, $this -> enclosure, $this -> escape];
|
|
if (PHP_VERSION_ID >= 80100)
|
|
$args[] = $this -> eol;
|
|
return call_user_func_array('fputcsv', $args) !== false;
|
|
}
|
|
|
|
/**
|
|
* fgetcsv wrapper in context of this export
|
|
* @param resource $fd The file pointer of the import
|
|
* @return array<string>|false The retrieved row or false in case of error or at the end of the
|
|
* file
|
|
*/
|
|
public function fgetcsv($fd) {
|
|
return fgetcsv(
|
|
$fd,
|
|
$this -> max_length,
|
|
$this -> separator,
|
|
$this -> enclosure,
|
|
$this -> escape
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Export items
|
|
* @param array<array<string,mixed>> $items The items to export
|
|
* @param resource|null $fd The file pointer where to export (optional, default: php://output)
|
|
* @return boolean
|
|
*/
|
|
public function export($items, $fd=null) {
|
|
if (!$fd) $fd = fopen('php://output', 'w');
|
|
$mapping = $this -> _fields_mapping();
|
|
$headers = [];
|
|
foreach ($mapping as $field)
|
|
$headers[] = $field['label'];
|
|
$success = $this -> fputcsv($fd, array_values($headers));
|
|
foreach($items as $item) {
|
|
$row = [];
|
|
foreach ($mapping as $key => $info) {
|
|
$row[] = call_user_func(
|
|
$info['to_string'],
|
|
array_key_exists($key, $item)?$item[$key]:null
|
|
);
|
|
}
|
|
$success = $success && $this -> fputcsv($fd, $row);
|
|
}
|
|
return $success;
|
|
}
|
|
|
|
/**
|
|
* Load items
|
|
* @param resource $fd The file pointer where to load data
|
|
* @return array<int,array<string,mixed>>|false The loaded items or false in case of error
|
|
*/
|
|
public function load($fd=null) {
|
|
if (!$fd) $fd = fopen('php://stdin', 'r');
|
|
$mapping = $this -> _fields_mapping();
|
|
$rows_mapping = false;
|
|
$line = 0;
|
|
$items = [];
|
|
$error = false;
|
|
while (($row = $this -> fgetcsv($fd)) !== FALSE) {
|
|
$line++;
|
|
if ($rows_mapping === false) {
|
|
$rows_mapping = [];
|
|
foreach($row as $idx => $field) {
|
|
$map = false;
|
|
foreach($mapping as $map_field => $map_info) {
|
|
if ($map_info['label'] == $field) {
|
|
$map = $map_field;
|
|
break;
|
|
}
|
|
}
|
|
if ($map) {
|
|
$rows_mapping[$idx] = $mapping[$map];
|
|
$rows_mapping[$idx]['name'] = $map;
|
|
unset($mapping[$map]);
|
|
continue;
|
|
}
|
|
Log :: warning(
|
|
"No corresponding field found for column '%s' (#%d), ignore it.",
|
|
$field, $idx
|
|
);
|
|
}
|
|
if (!$rows_mapping) {
|
|
Log :: warning("Invalid rows mapping loaded from line #%d", $line);
|
|
return false;
|
|
}
|
|
Log :: debug(
|
|
"CSV :: load(): Rows mapping established from row #%d : %s",
|
|
$line,
|
|
implode(
|
|
', ',
|
|
array_map(
|
|
function($idx, $info) {
|
|
return sprintf("%s => %s", $idx, $info["label"]);
|
|
},
|
|
array_keys($rows_mapping),
|
|
array_values($rows_mapping)
|
|
)
|
|
)
|
|
);
|
|
continue;
|
|
}
|
|
try {
|
|
$item = [];
|
|
foreach($rows_mapping as $idx => $field) {
|
|
$item[$field['name']] = call_user_func(
|
|
$field['from_string'],
|
|
$row[$idx]
|
|
);
|
|
}
|
|
Log :: trace("CSV :: load(): Item load from line #%d: %s", $line, implode_with_keys($item));
|
|
$items[] = $item;
|
|
}
|
|
catch (\Exception $e) {
|
|
Log :: error(
|
|
"Error occurred loading item from line #%d : %s\n%s",
|
|
$line,
|
|
$e->getMessage(),
|
|
print_r($row, true)
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
Log :: debug("CSV :: load(): %d item(s) loaded", count($items));
|
|
return $items;
|
|
}
|
|
}
|