Thanks to visit codestin.com
Credit goes to github.com

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions