Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checks for multiple virtual foreign keys in a model #12071

Closed
ghost opened this issue Aug 2, 2016 · 2 comments
Closed

Checks for multiple virtual foreign keys in a model #12071

ghost opened this issue Aug 2, 2016 · 2 comments
Labels
bug A bug report status: medium Medium
Milestone

Comments

@ghost
Copy link

ghost commented Aug 2, 2016

Hi,
I am reopening the issue for 3.0.x branch.

The problem was when model used more than one belongs to relations with virtual foreign keys. In some cases, when saving the model, check of the first virtual foreign key influences the check of other key. And instead of standard Phalcon message generated on the model, PDOException is thrown with error like this:

PDOException: SQLSTATE[23503]: Foreign key violation: 7 ERROR:  insert or update on table <table-name> violates foreign key constrain

Here are the steps to reproduce this behaviour. It's the minimal example I could get to work.

Let's have these 3 tables:

CREATE TABLE public.robot (
  id SERIAL,

  PRIMARY KEY (id)
);

CREATE TABLE public.head (
  id SERIAL,

  PRIMARY KEY (id)
);


CREATE TABLE public.part (
  id SERIAL,
  robot_id INT,
  head_id INT,

  PRIMARY KEY (id),
  FOREIGN KEY (robot_id) REFERENCES public.robot(id) ON DELETE CASCADE ON UPDATE CASCADE,
  FOREIGN KEY (head_id) REFERENCES head(id) ON DELETE CASCADE ON UPDATE CASCADE
);

And 3 models for these tables:

_Robot.php_

<?php
namespace App\Model;

class Robot extends \Phalcon\Mvc\Model
{

    protected $id;

    public function initialize()
    {
        $this->setSchema('public');
        $this->setSource('robot');
    }

    public function columnMap()
    {
        return [
            'id' => 'id',
        ];
    }

    public function setId($value)
    {
        $this->id = $value;

        return $this;
    }

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

_Head.php_

<?php
namespace App\Model;

class Head extends \Phalcon\Mvc\Model
{

    protected $id;

    public function initialize()
    {
        $this->setSchema('public');
        $this->setSource('head');
    }

    public function columnMap()
    {
        return [
            'id' => 'id',
        ];
    }

    public function setId($value)
    {
        $this->id = $value;

        return $this;
    }

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

and _Part.php_

<?php
namespace App\Model;

class Part extends \Phalcon\Mvc\Model
{

    protected $id;
    protected $robotId;
    protected $headId;

    public function initialize()
    {
        $this->setSchema('public');
        $this->setSource('part');

        $this->belongsTo(
            'robotId',
            '\App\Model\Robot',
            'id',
            [
                'alias' => 'robot',
                "foreignKey" => [
                    "allowNulls" => true,
                    "message"    => "Robot does not exists"
                ]
            ]
        );

        $this->belongsTo(
            'headId',
            '\App\Model\Head',
            'id',
            [
                'alias' => 'head',
                "foreignKey" => [
                    "allowNulls" => true,
                    "message"    => "Head does not exists"
                ]
            ]
        );
    }

    public function columnMap()
    {
        return [
            'id' => 'id',
            'robot_id' => 'robotId',
            'head_id' => 'headId',
        ];
    }

    public function setId($value)
    {
        $this->id = $value;

        return $this;
    }

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

    public function setRobotId($value)
    {
        $this->robotId = $value;

        return $this;
    }

    public function getRobotId()
    {
        return $this->robotId;
    }

    public function setHeadId($value)
    {
        $this->headId = $value;

        return $this;
    }

    public function getHeadId()
    {
        return $this->headId;
    }
}

And here comes the funny part. Let's have EXACTLY these records in the database:

Robot:
1/   id = 1
 2/  id = 2

Head:
1/   id = 1
 2/  id = 2

Part:
1/   id = 1, robot_id = 1, head_id = NULL

The NULL in the part table is pretty important.
Now, let's have this controller:

public function testAction()
{
    $part = \App\Model\Part::findFirst();

    $part->setHeadId(9999); // Invalid ID

    if (!$part->save()) {
        return [
            'error' => $part->getMessages()[0]->getMessage()
        ];
    }

    return [
        'error' => false
    ];
}

When head_id is originally NULL, PDOException with foreign key violation message is thrown.
But when you use this call instead, everything is fine.

$part->setRobotId(9999); // Also invalid ID

When you change the order of the calls to the belongsTo(...) methods and you register first the relation for the head table it works as well (that should be the problem of assigning value to validateWithNulls only once outside of the for loop).

@sergeyklay sergeyklay added this to the 3.0.1 milestone Aug 2, 2016
@sergeyklay
Copy link
Contributor

@crnix The issue is still relevant?

@ghost ghost closed this as completed Aug 11, 2016
@ghost
Copy link
Author

ghost commented Aug 11, 2016

Should be fine now. Sorry, thought the issue was closed automatically.

@niden niden added bug A bug report status: medium Medium and removed Bug - Medium labels Dec 23, 2019
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A bug report status: medium Medium
Projects
None yet
Development

No branches or pull requests

2 participants