-
-
Save DeRain/3ef101acff18dd71e177635ca666f18d to your computer and use it in GitHub Desktop.
Simple CSV Product importer for Sylius. Includes the product, the variant, channel pricing, taxons images and associations (1.0.0-beta.2)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace AppBundle\Command; | |
use Sylius\Component\Core\Model\ChannelPricingInterface; | |
use Sylius\Component\Core\Model\ProductInterface; | |
use Sylius\Component\Core\Model\ProductVariantInterface; | |
use Sylius\Component\Product\Model\ProductAssociationInterface; | |
use Sylius\Component\Product\Model\ProductAssociationTypeInterface; | |
use Sylius\Component\Taxonomy\Model\TaxonInterface; | |
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; | |
use Symfony\Component\Console\Input\InputArgument; | |
use Symfony\Component\Console\Input\InputInterface; | |
use Symfony\Component\Console\Output\OutputInterface; | |
use Symfony\Component\HttpFoundation\File\UploadedFile; | |
class CsvIterator { | |
const DELIM = ","; | |
const QUOTE = '"'; | |
protected $file; | |
protected $rows = array(); | |
public function __construct($file) { | |
$this->file = fopen($file, 'r'); | |
} | |
public function getFile() { | |
return $this->file; | |
} | |
public function parse() { | |
$headers = array_map('trim', fgetcsv($this->file, 4096, self::DELIM, self::QUOTE)); | |
while (!feof($this->file)) { | |
$row = array_map('trim', (array)fgetcsv($this->file, 4096, self::DELIM, self::QUOTE)); | |
if (count($headers) !== count($row)) { | |
continue; | |
} | |
$this->rows[] = array_combine($headers, $row); | |
} | |
return $this->rows; | |
} | |
} | |
class ImportProductsCommand extends ContainerAwareCommand | |
{ | |
private $locale = 'en_US'; | |
private $productFactory; | |
private $productRepository; | |
private $productManager; | |
private $pricingFactory; | |
private $associationFactory; | |
private $associationRepository; | |
private $associationTypeFactory; | |
private $associationTypeRepository; | |
private $taxonFactory; | |
private $taxonRepository; | |
private $taxonManager; | |
private $channel; | |
protected function configure() | |
{ | |
$this | |
->setName('import:products') | |
->setDescription('Import products from csv file') | |
->addArgument( | |
'csvFilePath', | |
InputArgument::REQUIRED, | |
'Specify path to CSV file' | |
) | |
; | |
} | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$this->productFactory = $this->getContainer()->get('sylius.factory.product'); | |
$this->productRepository = $this->getContainer()->get('sylius.repository.product'); | |
$this->productManager = $this->getContainer()->get('sylius.manager.product'); | |
$this->pricingFactory = $this->getContainer()->get('sylius.factory.channel_pricing'); | |
$this->associationFactory = $this->getContainer()->get('sylius.factory.product_association'); | |
$this->associationRepository = $this->getContainer()->get('sylius.repository.product_association'); | |
$this->associationTypeFactory = $this->getContainer()->get('sylius.factory.product_association_type'); | |
$this->associationTypeRepository = $this->getContainer()->get('sylius.repository.product_association_type'); | |
$this->taxonFactory = $this->getContainer()->get('sylius.factory.taxon'); | |
$this->taxonRepository = $this->getContainer()->get('sylius.repository.taxon'); | |
$this->taxonManager = $this->getContainer()->get('sylius.manager.taxon'); | |
$this->channel = $this->getContainer()->get('sylius.context.channel')->getChannel(); | |
/* AssociationType */ | |
/** @var ProductAssociationTypeInterface $associationType */ | |
if (!$associationType = $this->associationTypeRepository->findOneBy(['code' => 'related_product'])) { | |
$associationType = $this->associationTypeFactory->createNew(); | |
$associationType->setCode('related_product'); | |
$associationType->setName('Related Product'); | |
$this->associationTypeRepository->add($associationType); | |
} ; | |
/* CSV */ | |
$csvFilePath = $input->getArgument('csvFilePath'); | |
$csv = new CsvIterator($csvFilePath); | |
if ($csv->getFile() === false) { | |
die(sprintf('CSV file not valid. path: %s'.PHP_EOL, $csvFilePath)); | |
} | |
$count = 0; | |
foreach ($csv->parse() as $row) { | |
$code = trim($row['Product Code']); | |
$name = trim($row['Name']); | |
dump($code, $name); | |
/* Product - load or create */ | |
$output->writeln('<comment>Product</comment>'); | |
/** @var ProductInterface $product */ | |
if (!$product = $this->productRepository->findOneByCode($code, $this->locale)) { | |
$product = $this->productFactory->createWithVariant(); | |
} ; | |
$product->setCode($code); // Both Product and Variant needs a valid 'Code' | |
$product->setName($name); | |
$product->setDescription(trim($row['Full Description'])); | |
$product->setSlug(str_replace(' ', '', $code)); | |
$product->setVariantSelectionMethod($product::VARIANT_SELECTION_CHOICE); | |
$product->addChannel($this->channel); | |
/* Variant */ | |
$output->writeln('<comment>Variant</comment>'); | |
/** @var ProductVariantInterface $product_variant */ | |
$product_variant = $product->getVariants()[0]; | |
$product_variant->setCode($code); // Both Product and Variant needs a valid 'Code' | |
$product_variant->setWidth((int) trim($row['Width'])); | |
$product_variant->setWeight((int) trim($row['Weight'])); | |
// Thickness | |
// Length | |
/* Pricing - set per channel (only one in our case) */ | |
$output->writeln('<comment>Pricing</comment>'); | |
/** @var ChannelPricingInterface $channelPricing */ | |
if (!$channelPricing = $product_variant->getChannelPricingForChannel($this->channel)) { | |
$channelPricing = $this->pricingFactory->createNew(); | |
$product_variant->addChannelPricing($channelPricing); | |
} ; | |
$channelPricing->setChannelCode($this->channel->getCode()); | |
$channelPricing->setPrice($this->cleanPrice($row['Std Sell Price'])); | |
/* Taxons - Tree */ | |
$output->writeln('<comment>Taxons</comment>'); | |
$taxonTreeArray = array_filter(array( | |
$row['Product Group Level 1'], | |
$row['Product Group Level 2'], | |
$row['Product Group Level 3'], | |
$row['Product Group Level 4'], | |
$row['Product Group Level 5'], | |
) | |
); | |
$this->createTaxonTree($taxonTreeArray); | |
/* Taxons - Main */ | |
$output->writeln('<comment>Taxons</comment>'); | |
$taxon = $this->taxonRepository->findOneBySlug($this->cleanString($row['Product Group']), $this->locale); // case-insensitive | |
$product->setMainTaxon($taxon); | |
/* Image */ | |
$private_dir = realpath(__DIR__ . '/../..') . '/private/datafeed/current/'; | |
$import_dir = $private_dir . 'Product Info/'; | |
$matches = array(); | |
preg_match('/^\\\\\\\\2012SQL\\\\bistrack\\\\Product Info\\\\(.*)$/i', $row['udfWebImage'], $matches); | |
$imageUrl = $import_dir . str_replace('\\', '/', $matches[1]); | |
if (file_exists($imageUrl)) { | |
$output->writeln('<comment>Image</comment>'); | |
$product->addImage($this->getImage($imageUrl)); | |
} | |
/* Associations */ | |
$associatedProductCodes = array_filter(array( | |
$row['Related Product 1'], | |
$row['Related Product 2'], | |
$row['Related Product 3'], | |
$row['Related Product 4'], | |
$row['Related Product 5'], | |
) | |
); | |
/** @var ProductAssociationInterface $productAssociation */ | |
$productAssociation = $this->associationFactory->createNew(); | |
$productAssociation->setType($associationType); | |
foreach ($associatedProductCodes as $associatedProductCode) { | |
if ($associatedProduct = $this->productRepository->findOneByCode($associatedProductCode)) { | |
$productAssociation->addAssociatedProduct($associatedProduct); | |
} | |
} | |
$product->addAssociation($productAssociation); | |
$this->associationRepository->add($productAssociation); | |
/* Saving */ | |
$output->writeln('<comment>Saving</comment>'); | |
$this->productManager->persist($product); | |
if (0 === ++$count % 100) { | |
$this->productManager->flush(); | |
} | |
} | |
$this->productManager->flush(); | |
} | |
private function createTaxonTree($taxonTreeArray) | |
{ | |
$parent = null; | |
foreach ($taxonTreeArray as $taxonName) { | |
if ($taxon = $this->taxonRepository->findOneBySlug($this->cleanString($taxonName), $this->locale)) { | |
$parent = $taxon; | |
continue; | |
} | |
/** @var TaxonInterface $taxon */ | |
$taxon = $this->taxonFactory->createNew(); | |
$taxon->setCode($this->cleanString($taxonName)); | |
$taxon->setName($taxonName); | |
$taxon->setSlug($this->cleanString($taxonName)); | |
if ($parent) { | |
$taxon->setParent($parent); | |
} | |
$parent = $taxon; | |
$this->taxonManager->persist($taxon); | |
} | |
$this->taxonManager->flush(); | |
} | |
private function cleanPrice($price) | |
{ | |
return (int) str_replace([ | |
utf8_decode("£"), | |
utf8_decode("."), | |
], '', $price); | |
} | |
private function getImage($imageUrl) | |
{ | |
$fileName = substr($imageUrl, strrpos($imageUrl, '/') + 1); | |
$img = sys_get_temp_dir() . '/' . $fileName; | |
file_put_contents($img, file_get_contents($imageUrl)); | |
$imageEntity = $this->getContainer()->get('sylius.factory.product_image')->createNew(); | |
$imageEntity->setFile(new UploadedFile($img, $fileName)); | |
$this->getContainer()->get('sylius.image_uploader')->upload($imageEntity); | |
return $imageEntity; | |
} | |
private function cleanString($string) | |
{ | |
return strtolower(str_replace([' '], '_', $string)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment