CSV.php 5.5 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\migrate_source_csv\Plugin\migrate\source;

5
use Drupal\Component\Plugin\ConfigurablePluginInterface;
6
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
7
use Drupal\Component\Utility\NestedArray;
8 9
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
10
use Drupal\migrate\Plugin\MigrationInterface;
11 12 13 14 15 16 17 18 19 20 21

/**
 * Source for CSV.
 *
 * If the CSV file contains non-ASCII characters, make sure it includes a
 * UTF BOM (Byte Order Marker) so they are interpreted correctly.
 *
 * @MigrateSource(
 *   id = "csv"
 * )
 */
22
class CSV extends SourcePluginBase implements ConfigurablePluginInterface {
23 24 25 26 27 28 29 30 31

  /**
   * List of available source fields.
   *
   * Keys are the field machine names as used in field mappings, values are
   * descriptions.
   *
   * @var array
   */
32
  protected $fields = [];
33 34 35 36 37 38

  /**
   * List of key fields, as indexes.
   *
   * @var array
   */
39
  protected $keys = [];
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54
  /**
   * The file class to read the file.
   *
   * @var string
   */
  protected $fileClass = '';

  /**
   * The file object that reads the CSV file.
   *
   * @var \SplFileObject
   */
  protected $file = NULL;

55 56 57 58 59
  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
60
    $this->setConfiguration($configuration);
61 62

    // Path is required.
63
    if (empty($this->getConfiguration()['path'])) {
64 65 66 67
      throw new MigrateException('You must declare the "path" to the source CSV file in your source settings.');
    }

    // Key field(s) are required.
68
    if (empty($this->getConfiguration()['keys'])) {
69 70 71
      throw new MigrateException('You must declare "keys" as a unique array of fields in your source settings.');
    }

72
    $this->fileClass = $this->getConfiguration()['file_class'];
73 74 75
  }

  /**
76
   * Return a string representing the source file path.
77 78 79 80 81
   *
   * @return string
   *   The file path.
   */
  public function __toString() {
82
    return $this->getConfiguration()['path'];
83 84 85 86 87 88
  }

  /**
   * {@inheritdoc}
   */
  public function initializeIterator() {
89 90 91
    if (!file_exists($this->getConfiguration()['path'])) {
      throw new InvalidPluginDefinitionException($this->getPluginId(), sprintf('File path (%s) does not exist.', $this->getConfiguration()['path']));
    }
92
    // File handler using header-rows-respecting extension of SPLFileObject.
93
    $this->file = new $this->fileClass($this->getConfiguration()['path']);
94 95
    return $this->setupFile();
  }
96

97 98 99 100
  /**
   * @return \SplFileObject
   */
  protected function setupFile() {
101
    // Set basics of CSV behavior based on configuration.
102 103 104
    $delimiter = $this->getConfiguration()['delimiter'];
    $enclosure = $this->getConfiguration()['enclosure'];
    $escape = $this->getConfiguration()['escape'];
105
    $this->file->setCsvControl($delimiter, $enclosure, $escape);
106
    $this->file->setFlags($this->getConfiguration()['file_flags']);
107 108 109

    // Figure out what CSV column(s) to use. Use either the header row(s) or
    // explicitly provided column name(s).
110 111
    if ($this->getConfiguration()['header_row_count']) {
      $this->file->setHeaderRowCount($this->getConfiguration()['header_row_count']);
112 113

      // Find the last header line.
114 115
      $this->file->rewind();
      $this->file->seek($this->file->getHeaderRowCount() - 1);
116

117
      $row = $this->file->current();
118 119
      foreach ($row as $header) {
        $header = trim($header);
120
        $column_names[] = [$header => $header];
121
      }
122
      $this->file->setColumnNames($column_names);
123 124
    }
    // An explicit list of column name(s) will override any header row(s).
125 126
    if ($this->getConfiguration()['column_names']) {
      $this->file->setColumnNames($this->getConfiguration()['column_names']);
127 128
    }

129
    return $this->file;
130 131 132 133 134 135
  }

  /**
   * {@inheritdoc}
   */
  public function getIDs() {
136
    $ids = [];
137
    foreach ($this->getConfiguration()['keys'] as $delta => $value) {
138 139 140 141 142 143
      if (is_array($value)) {
        $ids[$delta] = $value;
      }
      else {
        $ids[$value]['type'] = 'string';
      }
144 145 146 147 148 149 150 151
    }
    return $ids;
  }

  /**
   * {@inheritdoc}
   */
  public function fields() {
152
    $fields = [];
153 154 155 156
    if (!$this->file) {
      $this->initializeIterator();
    }
    foreach ($this->file->getColumnNames() as $column) {
157
      $fields[key($column)] = reset($column);
158 159 160 161
    }

    // Any caller-specified fields with the same names as extracted fields will
    // override them; any others will be added.
162
    $fields = $this->getConfiguration()['fields'] + $fields;
163 164 165 166

    return $fields;
  }

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
  /**
   * {@inheritdoc}
   */
  public function getConfiguration() {
    return $this->configuration;
  }

  /**
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration) {
    // We must preserve integer keys for column_name mapping.
    $this->configuration = NestedArray::mergeDeepArray([$this->defaultConfiguration(), $configuration], TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'fields' => [],
      'keys' => [],
      'column_names' => [],
      'header_row_count' => 0,
      'file_flags' => \SplFileObject::READ_CSV | \SplFileObject::READ_AHEAD | \SplFileObject::DROP_NEW_LINE | \SplFileObject::SKIP_EMPTY,
      'delimiter' => ',',
      'enclosure' => '"',
      'escape' => '\\',
      'path' => '',
      'file_class' => 'Drupal\migrate_source_csv\CSVFileObject',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    return [];
  }
206
}