Skip to content

Wrong commit order in some relation cases #7006

@contentrail

Description

@contentrail

Issue was found after updating to v.2.6 and associated with improvements in CommitOrderCalculator class.
Here is the example to reproduce.
Objects:

  • Book
  • PCT with mandatory relation to Book (ManyToOne).
    Book has optional relation to the PCT object. Some Bookes has PCT and some not. But all PCT has relation to Book.
  • PCTFee as a child object of PCT (ManyToOne).

db_scheme

/**
 * @ORM\Entity
 * @ORM\Table(name="book")
 */
class Book
{
    /**
     * @var int
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $exchangeCode;

    /**
     * @var PCT
     * @ORM\OneToOne(targetEntity="PCT", cascade={"persist", "remove"})
     * @ORM\JoinColumn(name="paymentCardTransactionId", referencedColumnName="id")
     */
    private $paymentCardTransaction;

    public function __construct(int $id)
    {
        $this->id = $id;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExchangeCode(): ?string
    {
        return $this->exchangeCode;
    }

    public function setExchangeCode(string $exchangeCode = null): void
    {
        $this->exchangeCode = $exchangeCode;
    }

    public function getPaymentCardTransaction(): ?PCT
    {
        return $this->paymentCardTransaction;
    }

    public function setPaymentCardTransaction(PCT $paymentCardTransaction = null): void
    {
        $this->paymentCardTransaction = $paymentCardTransaction;
    }
}
/**
 * @ORM\Entity
 * @ORM\Table(name="pct")
 */
class PCT
{
    /**
     * @var int
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $id;

    /**
     * @var Book
     * @ORM\ManyToOne(targetEntity="Book")
     * @ORM\JoinColumn(name="bookingId", referencedColumnName="id", nullable=false)
     */
    private $book;

    /**
     * @var PCTFee[]
     * @ORM\OneToMany(targetEntity="PCTFee", mappedBy="pct", cascade={"persist", "remove"})
     * @ORM\OrderBy({"id" = "ASC"})
     */
    private $fees;

    public function __construct(int $id, Book $book)
    {
        $this->id = $id;
        $this->book = $book;
        $this->fees = new ArrayCollection();
    }

    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @return PCTFee[]|Collection
     */
    public function getFees(): Collection
    {
        return $this->fees;
    }

    public function addFee(PCTFee $fee)
    {
        $this->fees->add($fee);
    }

    public function removeFee(PCTFee $fee)
    {
        $this->fees->removeElement($fee);
    }

    public function getBook(): Book
    {
        return $this->book;
    }
}
/**
 * @ORM\Entity
 * @ORM\Table(name="pct_fee")
 */
class PCTFee
{
    /**
     * @var int
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var PCT
     * @ORM\ManyToOne(targetEntity="PCT", inversedBy="fees")
     * @ORM\JoinColumn(name="paymentCardTransactionId", referencedColumnName="id", nullable=false)
     */
    private $pct;

    public function __construct(PCT $pct)
    {
        $this->pct = $pct;
        $pct->addFee($this);
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getPCT(): PCT
    {
        return $this->pct;
    }
}

How to reproduce:
Find one Book (or create new).
Create new PCT object with even one PCTFee object for this Book object.

Try to flush.

        $booking = $this->em()->getRepository(Book::class)->find(1);
        if (!$booking) {
            $booking = new Book(1);
            $booking->setExchangeCode('1');
            $this->em()->persist($booking);
        }
        $id = (int) $booking->getExchangeCode();
        $id++;
        $booking->setExchangeCode((string) $id); // Change smth.

        $paymentCardTransaction = new PCT($id, $booking);

        $paymentCardTransactionFee = new PCTFee($paymentCardTransaction);

        $this->em()->persist($paymentCardTransaction);

        $this->save();

Error:

An exception occurred while executing 'INSERT INTO pct_fee (paymentCardTransactionId) VALUES (?)' with params [null]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'paymentCardTransactionId' cannot be null

I've try to figure out why and found that CommitOrderCalculator produce wrong commit order: PCTFee, Book, PCT.
It's wrong because PCTFee depends on PCT - PCT must be saved earlier.

New code in CommitOrderCalculator uses weights of relations. But weights is wrong.
We have 2 relations with different weights (nullable and not nullable):

  • PCT to Book with weight 0 (not nullable)
  • Book to PCT with weight 1 (nullable).

Before version 2.6 CommitOrderCalculator has checked both relations. But now it checks only relation with maximum weight!

I've removed code using weights from CommitOrderCalculator:

case self::IN_PROGRESS:
                    if (isset($adjacentVertex->dependencyList[$vertex->hash]) &&
                        $adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight) {
                        $adjacentVertex->state = self::VISITED;

                        $this->sortedNodeList[] = $adjacentVertex->value;
                    }
                    break;

and bug has disappeared.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions