/flex-importer

Example Flex Product Importer

Primary LanguagePHPMIT LicenseMIT

Flex Example Importer

This is a small showcase on how to bootstrap a simple product import service with Symfony Flex.

Requirements

  • PHP >= 7.1.3
  • Composer
  • SQLite

Steps

1. Create project

$ composer create-project symfony/skeleton:^4.0@beta flex-importer
$ cd flex-importer
$ composer require logger orm

gif

2. Configure your database

In this example we are using sqlite

$ cp .env.dist .env

Open .env file and set your DATABASE_URL to sqlite:///%kernel.project_dir%/var/data.db

# .env
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"

Enforce parameter parsing with resolve keyword in doctrine config:

# config/packages/doctrine.yaml
doctrine:
   dbal:
       url: '%env(resolve:DATABASE_URL)%'

More about Advanced environment variables

3. Implement a small example entity and setup your database

// src/Entity/Product.php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Product
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     */
    public $id;

    /** @ORM\Column */
    public $name;

    /** @ORM\Column(type="text") */
    public $description;

    /** @ORM\Column(type="integer") */
    public $price;

    /** @ORM\Column(type="integer") */
    public $taxRate;

    public static function fromArray(array $data): self
    {
        $instance = new static();

        $instance->id = $data['id'];
        $instance->name = $data['name'];
        $instance->description = $data['description'];
        $instance->price = $data['price'];
        $instance->taxRate = $data['taxRate'];

        return $instance;
    }
}

Setup your database

$ bin/console doctrine:schema:update --force

4. Bootstrap a small cli command as framework code

// src/Command/ProductImportCommand.php

namespace App\Command;

use App\ProductImporter;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class ProductImportCommand extends Command
{
    private $productImporter;

    public function __construct(ProductImporter $productImporter)
    {
        $this->productImporter = $productImporter;

        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->setName('app:product:import')
            ->addArgument('file', InputArgument::REQUIRED, 'Path to import file');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $file = $input->getArgument('file');

        $io->title('Product Import');

        $this->productImporter->importFile($file);

        $io->success('Successfully imported products.');

        return 0;
    }
}

5. Implement your import service as domain logic

namespace App;

use App\Entity\Product;
use Doctrine\Common\Persistence\ObjectManager;

class ProductImporter
{
    private $entityManager;

    public function __construct(ObjectManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function importFile(string $file): void
    {
        foreach ($this->readImportFile($file) as $data) {
            $product = Product::fromArray($data);

            $this->entityManager->persist($product);
            $this->entityManager->flush();
        }
    }

    private function readImportFile(string $file) : \Generator
    {
        $file = new \SplFileObject($file);
        $fields = $file->fgetcsv();

        while ($file->valid()) {
            $data = $file->fgetcsv();
            if (count($fields) === count($data)) {
                yield array_combine($fields, $data);
            }
        }
    }
}

6. Test your import with a sample csv file

"id", "name", "description", "price", "taxRate"
"123", "Soap", "Soap to wash your hands", "299", "19"
"234", "Milk", "Milk to eat with cereals", "119", "7"
"345", "Potatoes", "Potatos to cook and eat", "199", "7"
"456", "Pizza", "Frozen pizza for lazy Sundays", "219", "7"
"567", "Shirt", "To wear in summer", "999", "19"
"678", "Lego", "To play with", "2099", "19"
$ bin/console app:product:import products.csv

Product Import
==============

[OK] Successfully imported products.

gif