Skip to content

Instantly share code, notes, and snippets.

@nicanaca0
Last active May 1, 2024 11:05
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save nicanaca0/216abd17d8904941ccb0aaf1ef1e9c86 to your computer and use it in GitHub Desktop.
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)
<?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