Last active
May 1, 2024 11:05
-
-
Save nicanaca0/216abd17d8904941ccb0aaf1ef1e9c86 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 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