1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Tests;
6
7use JMS\Serializer\DeserializationContext;
8use JMS\Serializer\Expression\ExpressionEvaluator;
9use JMS\Serializer\Handler\HandlerRegistry;
10use JMS\Serializer\SerializationContext;
11use JMS\Serializer\SerializerBuilder;
12use JMS\Serializer\Tests\Fixtures\PersonSecret;
13use JMS\Serializer\Tests\Fixtures\PersonSecretWithVariables;
14use JMS\Serializer\Type\ParserInterface;
15use JMS\Serializer\Visitor\Factory\JsonSerializationVisitorFactory;
16use PHPUnit\Framework\TestCase;
17use Symfony\Component\ExpressionLanguage\ExpressionFunction;
18use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
19use Symfony\Component\Filesystem\Filesystem;
20
21class SerializerBuilderTest extends TestCase
22{
23    /** @var SerializerBuilder */
24    private $builder;
25    private $fs;
26    private $tmpDir;
27
28    public function testBuildWithoutAnythingElse()
29    {
30        $serializer = $this->builder->build();
31
32        self::assertEquals('"foo"', $serializer->serialize('foo', 'json'));
33        self::assertEquals('<?xml version="1.0" encoding="UTF-8"?>
34<result><![CDATA[foo]]></result>
35', $serializer->serialize('foo', 'xml'));
36
37        self::assertEquals('foo', $serializer->deserialize('"foo"', 'string', 'json'));
38        self::assertEquals('foo', $serializer->deserialize('<?xml version="1.0" encoding="UTF-8"?><result><![CDATA[foo]]></result>', 'string', 'xml'));
39    }
40
41    public function testWithCache()
42    {
43        self::assertFileNotExists($this->tmpDir);
44
45        self::assertSame($this->builder, $this->builder->setCacheDir($this->tmpDir));
46        $serializer = $this->builder->build();
47
48        self::assertFileExists($this->tmpDir);
49        self::assertFileExists($this->tmpDir . '/annotations');
50        self::assertFileExists($this->tmpDir . '/metadata');
51
52        $factory = $this->getField($serializer, 'factory');
53        self::assertAttributeSame(false, 'debug', $factory);
54        self::assertAttributeNotSame(null, 'cache', $factory);
55    }
56
57    public function testDoesAddDefaultHandlers()
58    {
59        $serializer = $this->builder->build();
60
61        self::assertEquals('"2020-04-16T00:00:00+00:00"', $serializer->serialize(new \DateTime('2020-04-16', new \DateTimeZone('UTC')), 'json'));
62        self::assertEquals('[1,2,3]', $serializer->serialize(new \ArrayIterator([1, 2, 3]), 'json'));
63    }
64
65    public function testCustomTypeParser()
66    {
67        $parserMock = $this->getMockBuilder(ParserInterface::class)
68            ->disableOriginalConstructor()
69            ->getMock();
70        $parserMock
71            ->method('parse')
72            ->willReturn(['name' => 'DateTimeImmutable', 'params' => [2 => 'd-Y-m']]);
73
74        $this->builder->setTypeParser($parserMock);
75
76        $serializer = $this->builder->build();
77
78        $result = $serializer->deserialize('"04-2020-10"', 'XXX', 'json');
79        self::assertInstanceOf(\DateTimeImmutable::class, $result);
80        self::assertEquals('2020-10-04', $result->format('Y-m-d'));
81    }
82
83    public function testDoesNotAddDefaultHandlersWhenExplicitlyConfigured()
84    {
85        self::assertSame($this->builder, $this->builder->configureHandlers(static function (HandlerRegistry $registry) {
86        }));
87
88        self::assertEquals('{}', $this->builder->build()->serialize(new \DateTime('2020-04-16'), 'json'));
89    }
90
91    /**
92     * @expectedException JMS\Serializer\Exception\UnsupportedFormatException
93     * @expectedExceptionMessage The format "xml" is not supported for serialization.
94     */
95    public function testDoesNotAddOtherVisitorsWhenConfiguredExplicitly()
96    {
97        self::assertSame(
98            $this->builder,
99            $this->builder->setSerializationVisitor('json', new JsonSerializationVisitorFactory())
100        );
101
102        $this->builder->build()->serialize('foo', 'xml');
103    }
104
105    public function testIncludeInterfaceMetadata()
106    {
107        self::assertFalse(
108            $this->getIncludeInterfaces($this->builder),
109            'Interface metadata are not included by default'
110        );
111
112        self::assertTrue(
113            $this->getIncludeInterfaces($this->builder->includeInterfaceMetadata(true)),
114            'Force including interface metadata'
115        );
116
117        self::assertFalse(
118            $this->getIncludeInterfaces($this->builder->includeInterfaceMetadata(false)),
119            'Force not including interface metadata'
120        );
121
122        self::assertSame(
123            $this->builder,
124            $this->builder->includeInterfaceMetadata(true)
125        );
126    }
127
128    public function testSetSerializationContext()
129    {
130        $contextFactoryMock = $this->getMockForAbstractClass('JMS\\Serializer\\ContextFactory\\SerializationContextFactoryInterface');
131        $context = new SerializationContext();
132        $context->setSerializeNull(true);
133
134        $contextFactoryMock
135            ->expects($this->once())
136            ->method('createSerializationContext')
137            ->will($this->returnValue($context));
138
139        $this->builder->setSerializationContextFactory($contextFactoryMock);
140
141        $serializer = $this->builder->build();
142
143        $result = $serializer->serialize(['value' => null], 'json');
144
145        self::assertEquals('{"value":null}', $result);
146    }
147
148    public function testSetDeserializationContext()
149    {
150        $contextFactoryMock = $this->getMockForAbstractClass('JMS\\Serializer\\ContextFactory\\DeserializationContextFactoryInterface');
151        $context = new DeserializationContext();
152
153        $contextFactoryMock
154            ->expects($this->once())
155            ->method('createDeserializationContext')
156            ->will($this->returnValue($context));
157
158        $this->builder->setDeserializationContextFactory($contextFactoryMock);
159
160        $serializer = $this->builder->build();
161
162        $result = $serializer->deserialize('{"value":null}', 'array', 'json');
163
164        self::assertEquals(['value' => null], $result);
165    }
166
167    public function testSetCallbackSerializationContextWithSerializeNull()
168    {
169        $this->builder->setSerializationContextFactory(static function () {
170            return SerializationContext::create()
171                ->setSerializeNull(true);
172        });
173
174        $serializer = $this->builder->build();
175
176        $result = $serializer->serialize(['value' => null], 'json');
177
178        self::assertEquals('{"value":null}', $result);
179    }
180
181    public function testSetCallbackSerializationContextWithNotSerializeNull()
182    {
183        $this->builder->setSerializationContextFactory(static function () {
184            return SerializationContext::create()
185                ->setSerializeNull(false);
186        });
187
188        $serializer = $this->builder->build();
189
190        $result = $serializer->serialize(['value' => null, 'not_null' => 'ok'], 'json');
191
192        self::assertEquals('{"not_null":"ok"}', $result);
193    }
194
195    public function expressionFunctionProvider()
196    {
197        return [
198            [
199                new ExpressionFunction('show_data', static function () {
200                    return 'true';
201                }, static function () {
202                    return true;
203                }),
204                '{"name":"mike"}',
205            ],
206            [
207                new ExpressionFunction('show_data', static function () {
208                    return 'false';
209                }, static function () {
210                    return false;
211                }),
212                '{"name":"mike","gender":"f"}',
213            ],
214        ];
215    }
216
217    /**
218     * @param ExpressionFunction $function
219     * @param string $json
220     *
221     * @dataProvider expressionFunctionProvider
222     */
223    public function testExpressionEngine(ExpressionFunction $function, $json)
224    {
225        $language = new ExpressionLanguage();
226        $language->addFunction($function);
227
228        $this->builder->setExpressionEvaluator(new ExpressionEvaluator($language));
229
230        $serializer = $this->builder->build();
231
232        $person = new PersonSecret();
233        $person->gender = 'f';
234        $person->name = 'mike';
235
236        self::assertEquals($json, $serializer->serialize($person, 'json'));
237    }
238
239    public function testExpressionEngineWhenDeserializing()
240    {
241        $language = new ExpressionLanguage();
242        $this->builder->setExpressionEvaluator(new ExpressionEvaluator($language));
243
244        $serializer = $this->builder->build();
245
246        $person = new PersonSecretWithVariables();
247        $person->gender = 'f';
248        $person->name = 'mike';
249
250        $serialized = $serializer->serialize($person, 'json');
251        self::assertEquals('{"name":"mike","gender":"f"}', $serialized);
252
253        $object = $serializer->deserialize($serialized, PersonSecretWithVariables::class, 'json');
254        self::assertEquals($person, $object);
255    }
256
257    protected function setUp()
258    {
259        $this->builder = SerializerBuilder::create();
260        $this->fs = new Filesystem();
261
262        $this->tmpDir = sys_get_temp_dir() . '/serializer';
263        $this->fs->remove($this->tmpDir);
264        clearstatcache();
265    }
266
267    protected function tearDown()
268    {
269        $this->fs->remove($this->tmpDir);
270    }
271
272    private function getField($obj, $name)
273    {
274        $ref = new \ReflectionProperty($obj, $name);
275        $ref->setAccessible(true);
276
277        return $ref->getValue($obj);
278    }
279
280    private function getIncludeInterfaces(SerializerBuilder $builder)
281    {
282        $factory = $this->getField($builder->build(), 'factory');
283
284        return $this->getField($factory, 'includeInterfaces');
285    }
286}
287