Passed
Pull Request — 2.6 (#7821)
by Marco
11:52
created

GH7820LineText::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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