phpexpertsinc/CSVSpeaker

View on GitHub
src/CSVWriter.php

Summary

Maintainability
A
35 mins
Test Coverage
<?php declare(strict_types=1);

/**
 * This file is part of CSVSpeaker, a PHP Experts, Inc., Project.
 *
 * Copyright © 2019-2021 PHP Experts, Inc.
 * Author: Theodore R. Smith <theodore@phpexperts.pro>
 *   GPG Fingerprint: 4BF8 2613 1C34 87AC D28F  2AD8 EB24 A91D D612 5690
 *   https://www.phpexperts.pro/
 *   https://github.com/phpexpertsinc/CSVSpeaker
 *
 * This file is licensed under the MIT License.
 */

namespace PHPExperts\CSVSpeaker;

use RuntimeException;

final class CSVWriter
{
    /** @var string The contents of the CSV file. */
    private $csv = '';

    /** @var string */
    private $header;

    private function convertToCsv(array $row, string $delimiter = ',', string $quoteSymbol = "\"", $eol = "\n", bool $useLegacyEscape = false): string
    {
        if (!($fh = fopen('php://memory', 'w+'))) {
            // @codeCoverageIgnoreStart - Untestable extreme edge case.
            throw new RuntimeException('Ran out of memory.');
            // @codeCoverageIgnoreEnd
        }

        // Support PHP v8.4+'s deprecation of the $escape parameter.
        // @see https://nyamsprod.com/blog/csv-and-php8-4/
        // @see https://archive.is/NIrda
        $escape = $useLegacyEscape === true ? "\\" : '';

        fputcsv($fh, $row, $delimiter, $quoteSymbol, $escape, $eol);
        rewind($fh);
        if (!($csv = stream_get_contents($fh))) {
            // @codeCoverageIgnoreStart - Untestable extreme edge case.
            throw new RuntimeException('Could not read the generated CSV file.');
            // @codeCoverageIgnoreEnd
        }
        fclose($fh);

        return $csv;
    }

    /**
     * Taken from https://stackoverflow.com/a/173479/430062.
     *
     * @param array $arr
     *
     * @return bool
     */
    private function isAssocArray(array $arr)
    {
        if (array() === $arr) {
            return false;
        }

        if (array_key_exists(0, $arr)) {
            return false;
        }

        return array_keys($arr) !== range(0, count($arr) - 1);
    }

    /**
     * WARNING: This method overwrite the CSV.
     *
     * @param array $columnNames
     */
    private function addHeader(array $columnNames): void
    {
        $newHeader = $this->convertToCsv($columnNames);

        if ($this->header === $newHeader) {
            return;
        }

        $this->csv .= $newHeader;
        $this->header = $newHeader;
    }

    public function addRow(array $row): void
    {
        if ($this->isAssocArray($row)) {
            $this->addHeader(array_keys($row));
        }

        $this->csv .= $this->convertToCsv($row);
    }

    public function addRows(array $rows): void
    {
        foreach ($rows as $row) {
            $this->addRow($row);
        }
    }

    public function getCSV(): string
    {
        return $this->csv;
    }
}