1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Tests\Serializer\Doctrine;
6
7use Doctrine\Common\Annotations\AnnotationReader;
8use Doctrine\Common\Annotations\Reader;
9use Doctrine\Common\Persistence\AbstractManagerRegistry;
10use Doctrine\Common\Persistence\ManagerRegistry;
11use Doctrine\DBAL\Connection;
12use Doctrine\DBAL\DriverManager;
13use Doctrine\ORM\Configuration;
14use Doctrine\ORM\EntityManager;
15use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
16use Doctrine\ORM\ORMException;
17use Doctrine\ORM\Tools\SchemaTool;
18use JMS\Serializer\Builder\CallbackDriverFactory;
19use JMS\Serializer\Builder\DefaultDriverFactory;
20use JMS\Serializer\Metadata\Driver\DoctrineTypeDriver;
21use JMS\Serializer\Naming\IdenticalPropertyNamingStrategy;
22use JMS\Serializer\Serializer;
23use JMS\Serializer\SerializerBuilder;
24use JMS\Serializer\Tests\Fixtures\Doctrine\SingleTableInheritance\Clazz;
25use JMS\Serializer\Tests\Fixtures\Doctrine\SingleTableInheritance\Organization;
26use JMS\Serializer\Tests\Fixtures\Doctrine\SingleTableInheritance\Person;
27use JMS\Serializer\Tests\Fixtures\Doctrine\SingleTableInheritance\School;
28use JMS\Serializer\Tests\Fixtures\Doctrine\SingleTableInheritance\Student;
29use JMS\Serializer\Tests\Fixtures\Doctrine\SingleTableInheritance\Teacher;
30use PHPUnit\Framework\TestCase;
31
32class IntegrationTest extends TestCase
33{
34    /** @var ManagerRegistry */
35    private $registry;
36
37    /** @var Serializer */
38    private $serializer;
39
40    public function testDiscriminatorIsInferredForEntityBaseClass()
41    {
42        $school = new School();
43        $json = $this->serializer->serialize($school, 'json');
44        self::assertEquals('{"type":"school"}', $json);
45
46        $deserialized = $this->serializer->deserialize($json, Organization::class, 'json');
47        self::assertEquals($school, $deserialized);
48    }
49
50    public function testDiscriminatorIsInferredForGenericBaseClass()
51    {
52        $student = new Student();
53        $json = $this->serializer->serialize($student, 'json');
54        self::assertEquals('{"type":"student"}', $json);
55
56        $deserialized = $this->serializer->deserialize($json, Person::class, 'json');
57        self::assertEquals($student, $deserialized);
58    }
59
60    public function testDiscriminatorIsInferredFromDoctrine()
61    {
62        /** @var EntityManager $em */
63        $em = $this->registry->getManager();
64
65        $student1 = new Student();
66        $student2 = new Student();
67        $teacher = new Teacher();
68        $class = new Clazz($teacher, [$student1, $student2]);
69
70        $em->persist($student1);
71        $em->persist($student2);
72        $em->persist($teacher);
73        $em->persist($class);
74        $em->flush();
75        $em->clear();
76
77        $reloadedClass = $em->find(get_class($class), $class->getId());
78        self::assertNotSame($class, $reloadedClass);
79
80        $json = $this->serializer->serialize($reloadedClass, 'json');
81        self::assertEquals('{"id":1,"teacher":{"id":1,"type":"teacher"},"students":[{"id":2,"type":"student"},{"id":3,"type":"student"}]}', $json);
82    }
83
84    protected function setUp()
85    {
86        $connection = $this->createConnection();
87        $entityManager = $this->createEntityManager($connection);
88
89        $this->registry = $registry = new SimpleManagerRegistry(
90            static function ($id) use ($connection, $entityManager) {
91                switch ($id) {
92                    case 'default_connection':
93                        return $connection;
94
95                    case 'default_manager':
96                        return $entityManager;
97
98                    default:
99                        throw new \RuntimeException(sprintf('Unknown service id "%s".', $id));
100                }
101            }
102        );
103
104        $this->serializer = SerializerBuilder::create()
105            ->setMetadataDriverFactory(new CallbackDriverFactory(
106                static function (array $metadataDirs, Reader $annotationReader) use ($registry) {
107                    $defaultFactory = new DefaultDriverFactory(new IdenticalPropertyNamingStrategy());
108
109                    return new DoctrineTypeDriver($defaultFactory->createDriver($metadataDirs, $annotationReader), $registry);
110                }
111            ))
112            ->build();
113
114        $this->prepareDatabase();
115    }
116
117    private function prepareDatabase()
118    {
119        /** @var EntityManager $em */
120        $em = $this->registry->getManager();
121
122        $tool = new SchemaTool($em);
123        $tool->createSchema($em->getMetadataFactory()->getAllMetadata());
124    }
125
126    private function createConnection()
127    {
128        return DriverManager::getConnection([
129            'driver' => 'pdo_sqlite',
130            'memory' => true,
131        ]);
132    }
133
134    private function createEntityManager(Connection $con)
135    {
136        $cfg = new Configuration();
137        $cfg->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader(), [
138            __DIR__ . '/../../Fixtures/Doctrine/SingleTableInheritance',
139        ]));
140        $cfg->setAutoGenerateProxyClasses(true);
141        $cfg->setProxyNamespace('JMS\Serializer\DoctrineProxy');
142        $cfg->setProxyDir(sys_get_temp_dir() . '/serializer-test-proxies');
143
144        return EntityManager::create($con, $cfg);
145    }
146}
147
148class SimpleManagerRegistry extends AbstractManagerRegistry
149{
150    private $services = [];
151    private $serviceCreator;
152
153    public function __construct($serviceCreator, $name = 'anonymous', array $connections = ['default' => 'default_connection'], array $managers = ['default' => 'default_manager'], $defaultConnection = null, $defaultManager = null, $proxyInterface = 'Doctrine\Common\Persistence\Proxy')
154    {
155        if (null === $defaultConnection) {
156            $defaultConnection = key($connections);
157        }
158        if (null === $defaultManager) {
159            $defaultManager = key($managers);
160        }
161
162        parent::__construct($name, $connections, $managers, $defaultConnection, $defaultManager, $proxyInterface);
163
164        if (!is_callable($serviceCreator)) {
165            throw new \InvalidArgumentException('$serviceCreator must be a valid callable.');
166        }
167        $this->serviceCreator = $serviceCreator;
168    }
169
170    public function getService($name)
171    {
172        if (isset($this->services[$name])) {
173            return $this->services[$name];
174        }
175
176        return $this->services[$name] = call_user_func($this->serviceCreator, $name);
177    }
178
179    public function resetService($name)
180    {
181        unset($this->services[$name]);
182    }
183
184    public function getAliasNamespace($alias)
185    {
186        foreach (array_keys($this->getManagers()) as $name) {
187            $manager = $this->getManager($name);
188
189            if ($manager instanceof EntityManager) {
190                try {
191                    return $manager->getConfiguration()->getEntityNamespace($alias);
192                } catch (ORMException $ex) {
193                    // Probably mapped by another entity manager, or invalid, just ignore this here.
194                }
195            } else {
196                throw new \LogicException(sprintf('Unsupported manager type "%s".', get_class($manager)));
197            }
198        }
199
200        throw new \RuntimeException(sprintf('The namespace alias "%s" is not known to any manager.', $alias));
201    }
202}
203