Passed
Pull Request — 2.6 (#7821)
by Marco
07:55
created

GH7820Test::testWillFindSongsInPaginator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 11
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Tests\ORM\Functional\Ticket;
6
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\DBAL\Platforms\AbstractPlatform;
9
use Doctrine\DBAL\Types\StringType;
10
use Doctrine\DBAL\Types\Type;
11
use Doctrine\ORM\Tools\Pagination\Paginator;
12
use Doctrine\Tests\OrmFunctionalTestCase;
13
use function array_map;
14
use function iterator_to_array;
15
16
/**
17
 * @group GH7820
18
 *
19
 * When using a {@see \Doctrine\ORM\Tools\Pagination\Paginator} to iterate over a query
20
 * that has entities with a custom DBAL type used in the identifier, then `$id->__toString()`
21
 * is used implicitly by {@see \PDOStatement::bindValue()}, instead of being converted by the
22
 * expected {@see \Doctrine\DBAL\Types\Type::convertToDatabaseValue()}.
23
 *
24
 * In order to reproduce this, you must have identifiers implementing
25
 * `#__toString()` (to allow {@see \Doctrine\ORM\UnitOfWork} to hash them) and other accessors
26
 * that are used by the custom DBAL type during DB/PHP conversions.
27
 *
28
 * If `#__toString()` and the DBAL type conversions are asymmetric, then the paginator will fail
29
 * to find records.
30
 *
31
 * Tricky situation, but this very much affects `ramsey/uuid-doctrine` and anyone relying on (for
32
 * example) the {@see \Ramsey\Uuid\Doctrine\UuidBinaryType} type.
33
 */
34
class GH7820Test extends OrmFunctionalTestCase
35
{
36
    private const SONG = [
37
        'What is this song all about?',
38
        'Can\'t figure any lyrics out',
39
        'How do the words to it go?',
40
        'I wish you\'d tell me, I don\'t know',
41
        'Don\'t know, don\'t know, don\'t know, I don\'t know!',
42
        'Don\'t know, don\'t know, don\'t know...',
43
    ];
44
45
    protected function setUp() : void
46
    {
47
        parent::setUp();
48
49
        if (! Type::hasType(GH7820LineTextType::class)) {
50
            Type::addType(GH7820LineTextType::class, GH7820LineTextType::class);
51
        }
52
53
        $this->setUpEntitySchema([GH7820Line::class]);
54
55
        foreach (self::SONG as $index => $line) {
56
            $this->_em->persist(new GH7820Line(GH7820LineText::fromText($line), $index));
57
        }
58
59
        $this->_em->flush();
60
    }
61
62
    public function testWillFindSongsInPaginator() : void
63
    {
64
        $query = $this->_em->getRepository(GH7820Line::class)
65
            ->createQueryBuilder('l')
66
            ->orderBy('l.lineNumber', Criteria::ASC);
67
68
        self::assertSame(
69
            self::SONG,
70
            array_map(static function (GH7820Line $line) : string {
71
                return $line->toString();
72
            }, iterator_to_array(new Paginator($query)))
73
        );
74
    }
75
}
76
77
/** @Entity */
78
class GH7820Line
79
{
80
    /**
81
     * @var GH7820LineText
82
     * @Id()
83
     * @Column(type="Doctrine\Tests\ORM\Functional\Ticket\GH7820LineTextType")
84
     */
85
    private $text;
86
87
    /**
88
     * @var int
89
     * @Column(type="integer")
90
     */
91
    private $lineNumber;
92
93
    public function __construct(GH7820LineText $text, int $index)
94
    {
95
        $this->text       = $text;
96
        $this->lineNumber = $index;
97
    }
98
99
    public function toString() : string
100
    {
101
        return $this->text->getText();
102
    }
103
}
104
105
final class GH7820LineText
106
{
107
    /** @var string */
108
    private $text;
109
110
    private function __construct(string $text)
111
    {
112
        $this->text = $text;
113
    }
114
115
    public static function fromText(string $text) : self
116
    {
117
        return new self($text);
118
    }
119
120
    public function getText() : string
121
    {
122
        return $this->text;
123
    }
124
125
    public function __toString() : string
126
    {
127
        return 'Line: ' . $this->text;
128
    }
129
}
130
131
final class GH7820LineTextType extends StringType
132
{
133
    public function convertToPHPValue($value, AbstractPlatform $platform)
134
    {
135
        $text = parent::convertToPHPValue($value, $platform);
136
137
        if (! \is_string($text)) {
138
            return $text;
139
        }
140
141
        return GH7820LineText::fromText($text);
142
    }
143
144
    public function convertToDatabaseValue($value, AbstractPlatform $platform)
145
    {
146
        if (! $value instanceof GH7820LineText) {
147
            return parent::convertToDatabaseValue($value, $platform);
148
        }
149
150
        return parent::convertToDatabaseValue($value->getText(), $platform);
151
    }
152
153
    /** {@inheritdoc} */
154
    public function getName() : string
155
    {
156
        return self::class;
157
    }
158
}
159