Skip to content

Commit

Permalink
Added Update
Browse files Browse the repository at this point in the history
  • Loading branch information
dotfry committed Dec 29, 2018
1 parent 96c6bc3 commit f14ec49
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 36 deletions.
56 changes: 56 additions & 0 deletions src/SixDreams/Bulk/AbstractBulk.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
declare(strict_types = 1);

namespace SixDreams\Bulk;

use SixDreams\Exceptions\FieldNotFoundException;

/**
* Class AbstractBulk
*/
abstract class AbstractBulk
{
/**
* Search property in class or it's subclasses and make it accessible.
*
* @param \ReflectionClass $class
* @param string $name
*
* @return \ReflectionProperty
*
* @throws FieldNotFoundException
*/
protected function getClassProperty(\ReflectionClass $class, string $name): \ReflectionProperty
{
if ($class->hasProperty($name)) {
$property = $class->getProperty($name);
$property->setAccessible(true);

return $property;
}

$subClass = $class->getParentClass();
if (!$class) {
throw new FieldNotFoundException($class->getName(), $name);
}

return $this->getClassProperty($subClass, $name);
}

/**
* Get all fields used in request.
*
* @param array $values
*
* @return array
*/
protected function getAllUsedFields(array &$values): array
{
$fields = [[]];
foreach ($values as $value) {
$fields[] = \array_keys($value);
}

return \array_flip(\array_flip(\array_merge(...$fields)));
}
}
46 changes: 10 additions & 36 deletions src/SixDreams/Bulk/BulkInsert.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
use SixDreams\Exceptions\WrongEntityException;

/**
* Class BulkInsert
* Allows to insert multiple doctrine entities to database.
*/
class BulkInsert
class BulkInsert extends AbstractBulk
{
public const FLAG_NONE = 0;
public const FLAG_IGNORE_MODE = 1 << 1;
Expand Down Expand Up @@ -76,7 +76,7 @@ public function addValue(array $data): BulkInsert
foreach ($this->metadata->getFields() as $field => $column) {
/** @noinspection NotOptimalIfConditionsInspection */
if (!$column->isNullable() && !\array_key_exists($field, $data)) {
throw new NullValueException($field, $this->class);
throw new NullValueException($this->class, $field);
}
}

Expand Down Expand Up @@ -121,7 +121,7 @@ public function addEntity(object $entity): BulkInsert
}

if (null === $value && !$column->isNullable()) {
throw new NullValueException($field, $this->class);
throw new NullValueException($this->class, $field);
}

$ret[$field] = $value;
Expand All @@ -141,6 +141,10 @@ public function addEntity(object $entity): BulkInsert
*/
public function execute(int $flags = self::FLAG_NONE, int $maxRows = self::DEFAULT_ROWS): ?string
{
if (!\count($this->values)) {
return null;
}

if ($flags & self::FLAG_IGNORE_DUPLICATES) {
$temp = [];
foreach ($this->values as $value) {
Expand All @@ -156,37 +160,11 @@ public function execute(int $flags = self::FLAG_NONE, int $maxRows = self::DEFAU
$lastInsertId = $this->executePartial($flags, $values);
$lastId = $lastId ?? $lastInsertId;
}
$this->values = [];

return $lastId;
}

/**
* Search property in class or it's subclasses and make it accessible.
*
* @param \ReflectionClass $class
* @param string $name
*
* @return \ReflectionProperty
*
* @throws FieldNotFoundException
*/
private function getClassProperty(\ReflectionClass $class, string $name): \ReflectionProperty
{
if ($class->hasProperty($name)) {
$property = $class->getProperty($name);
$property->setAccessible(true);

return $property;
}

$subClass = $class->getParentClass();
if (!$class) {
throw new FieldNotFoundException($class->getName(), $name);
}

return $this->getClassProperty($subClass, $name);
}

/**
* Executes insert to database and returns id of first inserted element.
*
Expand All @@ -197,11 +175,7 @@ private function getClassProperty(\ReflectionClass $class, string $name): \Refle
*/
private function executePartial(int $flags, array $values): ?string
{
$fields = [ [] ];
foreach ($values as $value) {
$fields[] = \array_keys($value);
}
$fields = \array_flip(\array_flip(\array_merge(...$fields)));
$fields = $this->getAllUsedFields($values);

$platform = $this->manager->getConnection()->getDatabasePlatform();

Expand Down
227 changes: 227 additions & 0 deletions src/SixDreams/Bulk/BulkUpdate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
<?php
declare(strict_types = 1);

namespace SixDreams\Bulk;

use Doctrine\ORM\EntityManagerInterface;
use SixDreams\DTO\Metadata;
use SixDreams\Exceptions\CannotChangeWhereException;
use SixDreams\Exceptions\FieldNotFoundException;
use SixDreams\Exceptions\NullValueException;
use SixDreams\Exceptions\WrongEntityException;

/**
* Allows to update multiple doctrine entities in database.
*/
class BulkUpdate extends AbstractBulk
{
/** @var EntityManagerInterface */
private $manager;

/** @var string */
private $class;

/** @var Metadata */
private $metadata;

/** @var array[] */
private $values = [];

/** @var \ReflectionClass */
private $reflection;

/** @var string */
private $whereField;

/** @var \ReflectionProperty[] */
private $cachedReflProps = [];

/**
* BulkUpdate constructor.
*
* @param EntityManagerInterface $manager
* @param string $class FCQN
*/
public function __construct(EntityManagerInterface $manager, string $class)
{
$this->manager = $manager;
$this->class = $class;
$this->metadata = MetadataLoader::load($manager->getClassMetadata($class));
$this->reflection = new \ReflectionClass($class);
$this->whereField = $this->metadata->getIdField();
}

/**
* Setter for WhereField.
*
* @param string $whereField
*
* @return BulkUpdate
*
* @throws CannotChangeWhereException
*/
public function setWhereField(string $whereField): BulkUpdate
{
if (\count($this->values)) {
throw new CannotChangeWhereException($this->class, $this->whereField, $whereField);
}

$this->whereField = $whereField;

return $this;
}



/**
* Adds single row to update queue.
*
* @param string|int $where
* @param array $data
*
* @return BulkUpdate
*
* @throws FieldNotFoundException
* @throws NullValueException
*/
public function addValue($where, array $data): BulkUpdate
{
foreach (\array_keys($data) as $name) {
if (!$this->metadata->hasField((string) $name)) {
throw new FieldNotFoundException($this->class, $name);
}
}
foreach ($this->metadata->getFields() as $field => $column) {
/** @noinspection NotOptimalIfConditionsInspection */
if (!$column->isNullable() && \array_key_exists($field, $data) && null === $data[$field]) {
throw new NullValueException($this->class, $field);
}
}

$this->values[$where] = $data;

return $this;
}

/**
* Adds entity to update queue. Using this method recommended with defined fields to update.
*
* @param object $entity
* @param array|null $fields
*
* @return BulkUpdate
*
* @throws WrongEntityException
*/
public function addEntity(object $entity, ?array $fields = null): BulkUpdate
{
if (\get_class($entity) !== $this->class) {
throw new WrongEntityException($this->class, $entity);
}

$fields = \array_flip($fields);

$where = null;
$data = [];
foreach ($this->metadata->getFields() as $field => $column) {
if ($field === $this->whereField) {
$where = $this->getClassProperty($this->reflection, $field)->getValue($entity);
continue;
}
if ($fields && !\array_key_exists($field, $fields)) {
continue;
}
$data[$field] = $this->getClassProperty($this->reflection, $field)->getValue($entity);
if (null === $data[$field] && !$column->isNullable()) {
throw new NullValueException($this->class, $field);
}
}

if (\count($data)) {
$this->values[$where] = $data;
}

return $this;
}

public function execute()
{
$values = $this->values;
if (!\count($values)) {
return null;
}

$fields = $this->getAllUsedFields($values);



$cases = [];
$thenId = 0;
foreach ($values as $when => $entity) {
//$cases[$field][] = \sprintf('CASE %s', $this->whereField);
$when = $this->escapeValue($when);
foreach ($fields as $field) {
if (\array_key_exists($field, $entity)) {
$cases[$field][] = \sprintf(
'WHEN %s THEN %s',
$when ?? ':W' . $thenId,
$this->escapeValue($entity[$field]) ?? ':T' . $thenId
);
} else {
$cases[$field][] = \sprintf('WHEN %s THEN %s', $when ?? ':W' . $thenId, $field); // todo: escape field
}
$thenId++;
}
}
foreach ($cases as $field => &$case) {
$case = \sprintf('SET %s = (%s)', $field, \implode(' ', $case));
}
unset($case);
$cases = \implode(', ', $cases);

$criterias = '';
$critId = 0;
foreach (\array_keys($values) as $criteria) {
if ('' !== $criterias) {
$criterias .= ', ';
}
$criterias .= $this->escapeValue($criteria) ?? ':C' . $critId;
}

$query = \sprintf(
'UPDATE %s %s WHERE %s IN (%s);',
$this->metadata->getTable(),
$cases,
$this->whereField, // todo: escape where & table
$criterias // todo: \implode(\array_keys($values)) with escape or binding..
);
/*
UPDATE table_name
SET text = (CASE id WHEN 1 THEN 'da'
WHEN 2 THEN 'net'
END)
SET text2 = (CASE id WHEN 1 THEN 'net'
WHEN 2 THEN text2)
WHERE id IN (1, 2);
UPDATE %s SET field = (CASE field WHEN ?|value THEN ?|value|field ...) ... WHERE field IN (?|value)
*/
// todo.
}

protected function escapeValue($value)
{
if (null === $value) {
return 'NULL';
}
if (is_numeric($value)) {
if (\strpos((string) $value, '.') !== false || \strpos((string) $value, ',') !== false) {
return (float) $value;
}

return (int) $value;
}

return null;
}
}
Loading

0 comments on commit f14ec49

Please sign in to comment.