123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- <?php
- /**
- * @file
- * Define base for migration sources.
- */
- /**
- * Abstract base class for source handling.
- *
- * Derived classes are expected to define __toString(), returning a string
- * describing the source and significant options. See
- * MigrateSourceSQL for an example.
- */
- abstract class MigrateSource implements Iterator {
- /**
- * The current row from the quey
- *
- * @var stdClass
- */
- protected $currentRow;
- /**
- * The primary key of the current row
- *
- * @var array
- */
- protected $currentKey;
- public function getCurrentKey() {
- return $this->currentKey;
- }
- /**
- * The Migration class currently invoking us, during rewind() and next().
- *
- * @var Migration
- */
- protected $activeMigration;
- /**
- * The MigrateMap class for the current migration.
- *
- * @var MigrateMap
- */
- protected $activeMap;
- /**
- * Number of rows intentionally ignored (prepareRow() returned FALSE)
- *
- * @var int
- */
- protected $numIgnored = 0;
- public function getIgnored() {
- return $this->numIgnored;
- }
- /**
- * Number of rows we've at least looked at.
- *
- * @var int
- */
- protected $numProcessed = 0;
- public function getProcessed() {
- return $this->numProcessed;
- }
- /**
- * Reset numIgnored back to 0.
- */
- public function resetStats() {
- $this->numIgnored = 0;
- }
- /**
- * Information on the highwater mark for the current migration, if any.
- *
- * @var array
- */
- protected $highwaterField;
- /**
- * List of source IDs to process.
- *
- * @var array
- */
- protected $idList = array();
- /**
- * Derived classes must implement fields(), returning a list of available
- * source fields.
- *
- * @return array
- * Keys: machine names of the fields (to be passed to addFieldMapping)
- * Values: Human-friendly descriptions of the fields.
- */
- abstract public function fields();
- /**
- * Whether this instance should cache the source count.
- *
- * @var boolean
- */
- protected $cacheCounts = FALSE;
- /**
- * Key to use for caching counts.
- *
- * @var string
- */
- protected $cacheKey;
- /**
- * Whether this instance should not attempt to count the source.
- *
- * @var boolean
- */
- protected $skipCount = FALSE;
- /**
- * By default, next() will directly read the map row and add it to the data
- * row. A source plugin implementation may do this itself (in particular, the
- * SQL source can incorporate the map table into the query) - if so, it should
- * set this TRUE so we don't duplicate the effort.
- *
- * @var bool
- */
- protected $mapRowAdded = FALSE;
- /**
- * Return a count of available source records, from the cache if appropriate.
- * Returns -1 if the source is not countable.
- *
- * @param boolean $refresh
- */
- public function count($refresh = FALSE) {
- if ($this->skipCount) {
- return -1;
- }
- if (!isset($this->cacheKey)) {
- $this->cacheKey = md5((string)$this);
- }
- // If a refresh is requested, or we're not caching counts, ask the derived
- // class to get the count from the source.
- if ($refresh || !$this->cacheCounts) {
- $count = $this->computeCount();
- cache_set($this->cacheKey, $count, 'cache');
- }
- else {
- // Caching is in play, first try to retrieve a cached count.
- $cache_object = cache_get($this->cacheKey, 'cache');
- if (is_object($cache_object)) {
- // Success
- $count = $cache_object->data;
- }
- else {
- // No cached count, ask the derived class to count 'em up, and cache
- // the result
- $count = $this->computeCount();
- cache_set($this->cacheKey, $count, 'cache');
- }
- }
- return $count;
- }
- /**
- * Derived classes must implement computeCount(), to retrieve a fresh count of
- * source records.
- */
- //abstract public function computeCount();
- /**
- * Class constructor.
- *
- * @param array $options
- * Optional array of options.
- */
- public function __construct($options = array()) {
- if (!empty($options['cache_counts'])) {
- $this->cacheCounts = TRUE;
- }
- if (!empty($options['skip_count'])) {
- $this->skipCount = TRUE;
- }
- if (!empty($options['cache_key'])) {
- $this->cacheKey = $options['cache_key'];
- }
- }
- /**
- * Default implementations of Iterator methods - many derivations will find
- * these adequate and will only need to implement rewind() and next()
- */
- /**
- * Implementation of Iterator::current() - called when entering a loop
- * iteration, returning the current row
- */
- public function current() {
- return $this->currentRow;
- }
- /**
- * Implementation of Iterator::key - called when entering a loop iteration, returning
- * the key of the current row. It must be a scalar - we will serialize
- * to fulfill the requirement, but using getCurrentKey() is preferable.
- */
- public function key() {
- return serialize($this->currentKey);
- }
- /**
- * Implementation of Iterator::valid() - called at the top of the loop, returning
- * TRUE to process the loop and FALSE to terminate it
- */
- public function valid() {
- return !is_null($this->currentRow);
- }
- /**
- * Implementation of Iterator::rewind() - subclasses of MigrateSource should
- * implement performRewind() to do any class-specific setup for iterating
- * source records.
- */
- public function rewind() {
- $this->activeMigration = Migration::currentMigration();
- $this->activeMap = $this->activeMigration->getMap();
- $this->numProcessed = 0;
- $this->numIgnored = 0;
- $this->highwaterField = $this->activeMigration->getHighwaterField();
- if ($this->activeMigration->getOption('idlist')) {
- $this->idList = explode(',', $this->activeMigration->getOption('idlist'));
- }
- else {
- $this->idList = array();
- }
- migrate_instrument_start(get_class($this) . ' performRewind');
- $this->performRewind();
- migrate_instrument_stop(get_class($this) . ' performRewind');
- $this->next();
- }
- /**
- * Implementation of Iterator::next() - subclasses of MigrateSource should
- * implement getNextRow() to retrieve the next valid source rocord to process.
- */
- public function next() {
- $this->currentKey = NULL;
- $this->currentRow = NULL;
- migrate_instrument_start(get_class($this) . ' getNextRow');
- while ($row = $this->getNextRow()) {
- migrate_instrument_stop(get_class($this) . ' getNextRow');
- // Populate the source key for this row
- foreach ($this->activeMap->getSourceKey() as $field_name => $field_schema) {
- $this->currentKey[$field_name] = $row->$field_name;
- }
- // Pick up the existing map row, if any, unless getNextRow() did it.
- if (!$this->mapRowAdded) {
- $map_row = $this->activeMap->getRowBySource($this->currentKey);
- // Add map info to the row, if present
- if ($map_row) {
- foreach ($map_row as $field => $value) {
- $field = 'migrate_map_' . $field;
- $row->$field = $value;
- }
- }
- }
- // First, determine if this row should be passed to prepareRow(), or skipped
- // entirely. The rules are:
- // 1. If there's an explicit idlist, that's all we care about (ignore
- // highwaters and map rows).
- $prepared = FALSE;
- if (!empty($this->idList)) {
- if (in_array(reset($this->currentKey), $this->idList)) {
- // In the list, fall through.
- }
- else {
- // Not in the list, skip it
- $this->currentRow = NULL;
- continue;
- }
- }
- // 2. If the row is not in the map (we have never tried to import it before),
- // we always want to try it.
- elseif (!isset($row->migrate_map_sourceid1)) {
- // Fall through
- }
- // 3. If the row is marked as needing update, pass it.
- elseif ($row->migrate_map_needs_update == MigrateMap::STATUS_NEEDS_UPDATE) {
- // Fall through
- }
- // 4. At this point, we have a row which has previously been imported and
- // not marked for update. If we're not using highwater marks, then we
- // will not take this row.
- elseif (empty($this->highwaterField)) {
- // No highwater, skip
- $this->currentRow = NULL;
- continue;
- }
- // 5. So, we are using highwater marks. Take the row if its highwater field
- // value is greater than the saved marked, otherwise skip it.
- else {
- // Call prepareRow() here, in case the highwaterField needs preparation
- if ($this->prepareRow($row) !== FALSE) {
- if ($row->{$this->highwaterField['name']} > $this->activeMigration->getHighwater()) {
- $this->currentRow = $row;
- break;
- }
- else {
- // Skip
- $this->currentRow = NULL;
- continue;
- }
- }
- else {
- $this->currentRow = NULL;
- }
- $prepared = TRUE;
- }
- // Allow the Migration to prepare this row. prepareRow() can return boolean
- // FALSE to ignore this row.
- if (!$prepared) {
- if ($this->prepareRow($row) !== FALSE) {
- // Finally, we've got a keeper.
- $this->currentRow = $row;
- break;
- }
- else {
- $this->currentRow = NULL;
- }
- }
- }
- migrate_instrument_stop(get_class($this) . ' getNextRow');
- if (!$this->currentRow) {
- $this->currentKey = NULL;
- }
- }
- /**
- * Give the calling migration a shot at manipulating, and possibly rejecting,
- * the source row.
- *
- * @return bool
- * FALSE if the row is to be skipped.
- */
- protected function prepareRow($row) {
- migrate_instrument_start(get_class($this->activeMigration) . ' prepareRow');
- $return = $this->activeMigration->prepareRow($row);
- migrate_instrument_stop(get_class($this->activeMigration) . ' prepareRow');
- // We're explicitly skipping this row - keep track in the map table
- if ($return === FALSE) {
- $this->activeMigration->getMap()->saveIDMapping($row, array(NULL),
- MigrateMap::STATUS_IGNORED);
- $this->numIgnored++;
- $this->currentRow = NULL;
- $this->currentKey = NULL;
- }
- else {
- $return = TRUE;
- }
- $this->numProcessed++;
- return $return;
- }
- }
|