CSV.php 5.71 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
   * Setup the file.
   *
100
   * @return \SplFileObject
101
   *   Returns the file object.
102 103
   */
  protected function setupFile() {
104
    // Set basics of CSV behavior based on configuration.
105 106 107
    $delimiter = $this->getConfiguration()['delimiter'];
    $enclosure = $this->getConfiguration()['enclosure'];
    $escape = $this->getConfiguration()['escape'];
108
    $this->file->setCsvControl($delimiter, $enclosure, $escape);
109
    $this->file->setFlags($this->getConfiguration()['file_flags']);
110 111 112

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

      // Find the last header line.
117 118
      $this->file->rewind();
      $this->file->seek($this->file->getHeaderRowCount() - 1);
119

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

132
    return $this->file;
133 134 135 136 137
  }

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

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

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

    return $fields;
  }

170 171 172 173 174 175 176
  /**
   * {@inheritdoc}
   */
  public function getConfiguration() {
    return $this->configuration;
  }

177 178 179 180 181 182 183 184 185 186
  /**
   * Gets the file object.
   *
   * @return \SplFileObject
   *   The file object.
   */
  public function getFile() {
    return $this->file;
  }

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
  /**
   * {@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 [];
  }
219

220
}