$data */ public function process(array $configuration, \Generator $data): void { $file = new \SplFileObject('php://output', 'w'); // backward compatibility with old CsvFormat $separator = $configuration['separator'] ?? ','; $header = $configuration['header'] ?? false; $enclosureCustom = $configuration['enclosure-custom'] ?? false; $enclosure = $configuration['enclosure'] ?? '"'; $escape = $configuration['escape'] ?? '\\'; if ($timestamp = $configuration['timestamp'] ?? false) { $file->fwrite('# timestamp '.$timestamp.PHP_EOL); } foreach ($this->getFlattenedGenerator($configuration, $data) as $key => $item) { if (empty($item)) { continue; } // add header if ($header && $key === 0) { if ($enclosureCustom) { $file->fwrite(implode($separator, array_map([$this, 'encodeValue'], array_keys($item)))."\r\n"); } else { $file->fputcsv(array_keys($item), $separator, $enclosure, $escape); } } // add data row if ($enclosureCustom) { $file->fwrite(implode($separator, $item)."\r\n"); } else { $file->fputcsv($item, $separator, $enclosure, $escape); } } } protected function getFlattenedGenerator(array $configuration, \Generator $data): \Generator { foreach ($data as $item) { yield $this->prepareItem( $configuration, $item, $this->toFlattenArray($item->asSimpleArray()) ); } } /** * Converts multi-dimensional array to simple flatten array. */ private function toFlattenArray(array $data): array { $flattened = []; foreach ($data as $key => $value) { if (is_array($value)) { $flattened[$key] = $this->formatArrayAsMultilineString($value); } else { $flattened[$key] = $value === null ? '' : $value; } } return $flattened; } private function formatArrayAsMultilineString(array $array): string { $formatted = []; foreach ($array as $item) { if (is_array($item)) { $lines = []; foreach ($item as $value) { $lines[] = is_array($value) ? $this->formatArrayAsMultilineString($value) : $value; } $formatted[] = implode("\n\n", $lines); } else { $formatted[] = $item; } } return implode("\n", $formatted); } /** * Prepares item by its configuration in attributes. */ private function prepareItem(array $configuration, FeedItem $original, array $item): array { $newItem = []; $enclosureCustom = $configuration['enclosure-custom'] ?? false; foreach ($item as $key => $value) { $attributes = $original->getAttributes($key); // replace key by `override-name` attribute if (!empty($attributes['override-name'])) { $key = $attributes['override-name']; } $forceEnclosure = $attributes['enclosure'] ?? false; $doubleQuotesWithoutEscaping = $attributes['double-quotes-without-escaping'] ?? false; $tripleQuotesWithoutEscaping = $attributes['triple-quotes-without-escaping'] ?? false; if ($enclosureCustom && ($forceEnclosure || $forceEnclosure === '') && !empty($value)) { if ($doubleQuotesWithoutEscaping || $doubleQuotesWithoutEscaping === '') { $value = $this->doubleQuotesWithoutEscaping($value); } elseif ($tripleQuotesWithoutEscaping || $tripleQuotesWithoutEscaping === '') { $value = $this->tripleQuotesWithoutEscaping($value); } else { $value = $this->encodeValue($value); } } $newItem[$key] = $value; } return $newItem; } protected function doubleQuotesWithoutEscaping(string $value): string { // remove any ESCAPED double quotes within string. $value = str_replace('\\"', '"', $value); // then replace double quotes with two double quotes $value = str_replace('"', '""', $value); // force wrap value in quotes and return return '"'.$value.'"'; } private function tripleQuotesWithoutEscaping(string $value): string { // remove any ESCAPED double quotes within string. $value = str_replace('\\"', '"', $value); // then replace double quotes with triple double quotes $value = str_replace('"', '"""', $value); // force wrap value in quotes and return return '"'.$value.'"'; } private function encodeValue(string $value): string { // remove any ESCAPED double quotes within string. $value = str_replace('\\"', '"', $value); // then force escape these same double quotes And Any UNESCAPED Ones. $value = str_replace('"', '\"', $value); // force wrap value in quotes and return return '"'.$value.'"'; } }