ikucheriavenko
3/23/2018 - 4:27 PM

Repository upsert

Upsert native sql

<?php

declare(strict_types=1);

/**
 * Created by PhpStorm.
 * User: Ivan Kucheriavenko
 * Date: 3/4/18
 * Time: 7:14 AM.
 */

namespace Crawler\Doctrine\DBAL;

/**
 * Class ConnectionHelper.
 */
class ConnectionHelper
{
    /**
     * Extract array of types for values on insert/update.
     *
     * @param array $values
     *
     * @return array
     */
    public function extractTypeValues(array $values): array
    {
        $types = array();

        foreach ($values as $value) {
            $types[] = $this->guessType($value);
        }

        return $types;
    }

    /**
     * @param mixed $columnValue
     *
     * @return mixed
     */
    public function guessType($columnValue)
    {
        return is_string($columnValue) ? \PDO::PARAM_STR
                    : (is_integer($columnValue) ? \PDO::PARAM_INT
                        : (is_bool($columnValue) ? \PDO::PARAM_BOOL
                            : (is_null($columnValue) ? \PDO::PARAM_NULL : null)));
    }
}
<?php

declare(strict_types=1);

/**
 * Created by PhpStorm.
 * User: ivan kucheriavenko
 * Date: 02.03.18
 * Time: 18:50.
 */

namespace Crawler\Repository;

use Crawler\Doctrine\DBAL\ConnectionHelper;
use Crawler\DTO\ParsedEntryArray;
use Crawler\Entity\ParsedEntry;
use Crawler\Entity\Product;
use DateTime;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Inflector\Inflector;
use Doctrine\Common\Persistence\ManagerRegistry;

/**
 * Class ParsedEntryRepository.
 */
class ParsedEntryRepository extends ServiceEntityRepository
{
    /** @var ConnectionHelper */
    private $connectionHelper;

    /**
     * {@inheritdoc}
     */
    public function __construct(ManagerRegistry $registry, ConnectionHelper $connectionHelper)
    {
        parent::__construct($registry, ParsedEntry::class);

        $this->connectionHelper = $connectionHelper;
    }

    /**
     * @param ArrayCollection $entries
     *
     * @return int
     */
    public function addOrIgnoreMultiple(ArrayCollection $entries)
    {
        $affectedCount = 0;
        if ($entries->isEmpty()) {
            return $affectedCount;
        }

        $conn = $this->_em->getConnection();
        $tableName = $this->_em->getClassMetadata(ParsedEntry::class)->getTableName();

        $entries = $entries
            ->filter(function ($entry) {
                return $entry instanceof ParsedEntry;
            })
            ->map(function ($entry) {
                /** @var ParsedEntry $entry */
                $entryArr = $entry->toArray();
                $keys = array_map(function ($key) {
                    return Inflector::tableize($key);
                }, array_keys($entryArr));
                return array_combine($keys, array_values($entryArr));
            })
            ->toArray()
        ;

        $set = [];
        $values = [];
        foreach ($entries as $index => $array) {
            $set[] = '('.implode(', ', array_fill(0, count($array), '?')).')';
            $values = array_merge($values, array_values($array));
        }

        $first = reset($entries);
        $columns = array_keys($first);

        $sql = 'INSERT IGNORE INTO '.$tableName.' ('.implode(', ', $columns).') VALUES '
            .implode(', ', $set);

        $affectedCount = $conn->executeUpdate(
            $sql,
            $values,
            $this->connectionHelper->extractTypeValues($values)
        );

        return $affectedCount;
    }

    /**
     * @param Product $product
     *
     * @return mixed
     */
    public function findAllByProductAndCreatedBetween(Product $product, DateTime $fromDate, DateTime $toDate)
    {
        $qb = $this->createQueryBuilder('pe');

        return $qb
            ->select('partial pe.{id}')
            ->where('pe.product = :product')
            ->andWhere(
                $qb->expr()->between('pe.createdAt', ':fromDate', ':toDate')
            )
            ->orderBy('pe.createdAt', 'DESC')
            ->setParameter('product', $product)
            ->setParameter('fromDate', $fromDate)
            ->setParameter('toDate', $toDate)
            ->getQuery()
            ->getResult();
    }
}