Now that our plugin configuration works—and uploaded files are properly managed and marked as used—it's time to implement the getData()method by which we process the CSV file of the Importer entity. The result needs to be an array of product information as expected by the import() method we saw earlier. So we can have something like this:
/** * Loads the product data from the remote URL. * * @return array */ private function getData() { /** @var \Drupal\products\Entity\ImporterInterface $importer_config */ $importer_config = $this->configuration['config']; $config = $importer_config->getPluginConfiguration(); $fids = isset($config['file']) ? $config['file'] : []; if (!$fids) { return NULL; } $fid = reset($fids); /** @var \Drupal\file\FileInterface $file */ $file = $this->entityTypeManager->getStorage('file')->load($fid); $wrapper = $this->streamWrapperManager->getViaUri($file->getFileUri()); if (!$wrapper) { return NULL; } $url = $wrapper->realpath(); $spl = new \SplFileObject($url, 'r'); $data = []; while (!$spl->eof()) { $data[] = $spl->fgetcsv(); } $products = []; $header = []; foreach ($data as $key => $row) { if ($key == 0) { $header = $row; continue; } if ($row[0] == "") { continue; } $product = new \stdClass(); foreach ($header as $header_key => $label) { $product->{$label} = $row[$header_key]; } $products[] = $product; } return $products; }
First, quite expectedly, we check for the existence of the File ID in the Importer entity and load the corresponding File entity based on that. To do this, we use the entity manager we injected into the plugin base class. But then comes something new.
Once we have the File entity, we can ask it its URI, which will return something like this: public://products.csv. This is what is stored in the database. But in order to turn that into something useful, we need to use the stream wrapper that defines this filesystem. And to get that, we use the StreamWrapperManager service (stream_wrapper_manager) which has a handy method of returning the stream wrapper instance responsible for a given URI—getViaUri(). And once we have our StreamWrapperInterface, we can use its realpath() method to get the local path of the resource. We will come back to stream wrappers a bit later in this chapter and it will make more sense. But for the moment, it's enough to understand that we are translating a URI in the scheme://target format into a useful path that we can use to create a new PHP-native SplFileObject instance, which, in turn, we can use to process the CSV file easily.
With three lines of code we are basically done getting all the rows from the CSV into the $data array. However, we also want to make this data look a bit more like what the JSON resource looked like—a map where the keys are the field names and the values are the respective product data. And we also want this map to contain PHP standard objects instead of arrays. Therefore, we loop through the data, establish the CSV header values, and use those as the keys in each row of a new $products array of objects. Our end result will look exactly like the product information coming from the decoded JSON response.
And with this we are done. Well, not quite. We still need to inject the StreamWrapperManager service into our plugin. And to do that, we need to make sure we are injecting also all the things that the parent class needs and passing them along:
/** * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface */ protected $streamWrapperManager; /** * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entityTypeManager, ClientInterface $httpClient, StreamWrapperManagerInterface $streamWrapperManager) { parent::__construct($configuration, $plugin_id, $plugin_definition, $entityTypeManager, $httpClient); $this->streamWrapperManager = $streamWrapperManager; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('entity_type.manager'), $container->get('http_client'), $container->get('stream_wrapper_manager') ); }
And the new use statements at the top:
use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use GuzzleHttp\ClientInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
Nothing we don't yet know how to do. However, there is one thing I'd like to point out here. In Chapter 7, Your Own Custom Entity and Plugin Types, I mentioned how, at the time, I believed the Guzzle HTTP Client is a service that would be useful to all Importer plugins. Well, I was clearly wrong, as the CSV-based one we just created now doesn't need it. So there is no reason why it should be injected into it. What we need to do here is remove this dependency from the base plugin class and only use it in the JSON importer. However, I leave this up to you as homework.
Our CSV Importer plugin is now complete. If we did everything correctly, we can now create a new Importer entity that uses it, upload a correct CSV file, and import some Product entities via our Drush command. How neat.