Completed
Pull Request — master (#5823)
by Mikhail
13:41
created

DDC2306Test::testIssue()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 24

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 36
rs 8.8571
cc 1
eloc 24
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\ORM\Functional\Ticket;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
7
/**
8
 * @group DDC-2306
9
 */
10
class DDC2306Test extends \Doctrine\Tests\OrmFunctionalTestCase
11
{
12
    /**
13
     * {@inheritDoc}
14
     */
15
    protected function setUp()
16
    {
17
        parent::setUp();
18
19
        $this->_schemaTool->createSchema(array(
20
            $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2306Zone'),
21
            $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2306User'),
22
            $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2306Address'),
23
            $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC2306UserAddress'),
24
        ));
25
    }
26
27
    /**
28
     * Verifies that when eager loading is triggered, proxies are kept managed.
29
     *
30
     * The problem resides in the refresh hint passed to {@see \Doctrine\ORM\UnitOfWork::createEntity},
31
     * which, as of DDC-1734, causes the proxy to be marked as un-managed.
32
     * The check against the identity map only uses the identifier hash and the passed in class name, and
33
     * does not take into account the fact that the set refresh hint may be for an entity of a different
34
     * type from the one passed to {@see \Doctrine\ORM\UnitOfWork::createEntity}
35
     *
36
     * As a result, a refresh requested for an entity `Foo` with identifier `123` may cause a proxy
37
     * of type `Bar` with identifier `123` to be marked as un-managed.
38
     */
39
    public function testIssue()
40
    {
41
        $zone          = new DDC2306Zone();
42
        $user          = new DDC2306User;
43
        $address       = new DDC2306Address;
44
        $userAddress   = new DDC2306UserAddress($user, $address);
45
        $user->zone    = $zone;
46
        $address->zone = $zone;
47
48
        $this->_em->persist($zone);
49
        $this->_em->persist($user);
50
        $this->_em->persist($address);
51
        $this->_em->persist($userAddress);
52
        $this->_em->flush();
53
        $this->_em->clear();
54
55
        /* @var $address DDC2306Address */
56
        $address = $this->_em->find(__NAMESPACE__ . '\\DDC2306Address', $address->id);
57
        /* @var $user DDC2306User|\Doctrine\ORM\Proxy\Proxy */
58
        $user    = $address->users->first()->user;
59
60
        $this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $user);
61
        $this->assertInstanceOf(__NAMESPACE__ . '\\DDC2306User', $user);
62
63
        $userId = $user->id;
64
65
        $this->assertNotNull($userId);
66
67
        $user->__load();
0 ignored issues
show
Bug introduced by
The method __load does only exist in Doctrine\ORM\Proxy\Proxy, but not in Doctrine\Tests\ORM\Functional\Ticket\DDC2306User.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
68
69
        $this->assertEquals(
70
            $userId,
71
            $user->id,
72
            'As of DDC-1734, the identifier is NULL for un-managed proxies. The identifier should be an integer here'
73
        );
74
    }
75
}
76
77
/** @Entity */
78
class DDC2306Zone
79
{
80
    /** @Id @Column(type="integer") @GeneratedValue */
81
    public $id;
82
}
83
84
/**
85
 * @Entity
86
 */
87
class DDC2306User
88
{
89
    /** @Id @Column(type="integer") @GeneratedValue */
90
    public $id;
91
92
    /**
93
     * @var DDC2306UserAddress[]|\Doctrine\Common\Collections\Collection
94
     *
95
     * @OneToMany(targetEntity="DDC2306UserAddress", mappedBy="user")
96
     */
97
    public $addresses;
98
99
    /** @ManyToOne(targetEntity="DDC2306Zone", fetch="EAGER") */
100
    public $zone;
101
102
    /** Constructor */
103
    public function __construct() {
104
        $this->addresses = new ArrayCollection();
105
    }
106
}
107
108
/** @Entity */
109
class DDC2306Address
110
{
111
    /** @Id @Column(type="integer") @GeneratedValue */
112
    public $id;
113
114
    /**
115
     * @var DDC2306UserAddress[]|\Doctrine\Common\Collections\Collection
116
     *
117
     * @OneToMany(targetEntity="DDC2306UserAddress", mappedBy="address", orphanRemoval=true)
118
     */
119
    public $users;
120
121
    /** @ManyToOne(targetEntity="DDC2306Zone", fetch="EAGER") */
122
    public $zone;
123
124
    /** Constructor */
125
    public function __construct() {
126
        $this->users = new ArrayCollection();
127
    }
128
}
129
130
/** @Entity */
131
class DDC2306UserAddress
132
{
133
    /** @Id @Column(type="integer") @GeneratedValue */
134
    public $id;
135
136
    /** @ManyToOne(targetEntity="DDC2306User") */
137
    public $user;
138
139
    /** @ManyToOne(targetEntity="DDC2306Address", fetch="LAZY") */
140
    public $address;
141
142
    /** Constructor */
143
    public function __construct(DDC2306User $user, DDC2306Address $address)
144
    {
145
        $this->user    = $user;
146
        $this->address = $address;
147
148
        $user->addresses->add($this);
149
        $address->users->add($this);
150
    }
151
}