diff --git a/.travis.yml b/.travis.yml
index b8f464d..4ca6e0b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
language: php
php:
- - 7.0
- 7.1
- 7.2
@@ -9,7 +8,7 @@ branches:
only:
- master
- develop
- - /^v\d+\.\d+\.\d+$/
+ - /^d+\.\d+$/
cache:
directories:
diff --git a/composer.json b/composer.json
index 8047bcb..89e9d92 100644
--- a/composer.json
+++ b/composer.json
@@ -10,22 +10,32 @@
}
],
"require": {
+ "php": "^7.1",
"symfony/property-access": "^3.0|^4.0"
},
"require-dev": {
"doctrine/orm": "^2.4,>=2.4.5",
- "phpunit/phpunit": "^6.3",
+ "phpunit/phpunit": "^7.0",
"friendsofphp/php-cs-fixer": "^2.7",
- "phpmd/phpmd": "^2.6"
+ "phpmd/phpmd": "^2.6",
+ "phpstan/phpstan": "^0.9.2",
+ "symfony/var-dumper": "^4.0",
+ "symfony/yaml": "^4.0"
},
"suggest": {
"doctrine/orm": "^2.4,>=2.4.5",
"webonyx/graphql-php": "^0.0,>=0.9"
},
"autoload": {
- "psr-4": { "Xsolve\\Associate\\": "lib" }
+ "psr-4": {
+ "Xsolve\\Associate\\": "lib",
+ "Xsolve\\LegacyAssociate\\": "legacyLib"
+ }
},
"autoload-dev": {
- "psr-4": { "Xsolve\\AssociateTests\\": "tests" }
+ "psr-4": {
+ "Xsolve\\AssociateTests\\": "tests",
+ "Xsolve\\LegacyAssociateTests\\": "legacyTests"
+ }
}
}
diff --git a/draft-1.php b/draft-1.php
new file mode 100644
index 0000000..41f19e0
--- /dev/null
+++ b/draft-1.php
@@ -0,0 +1,96 @@
+getTargets($sources, $path);
+
+$sourcesToTargetsMap = $collector->getSourcesToTargetsMap($sources, $path);
+$targets1 = $sourcesToTargetsMap->get([$source1]);
+$targets1And2 = $sourcesToTargetsMap->get([$source1, $source2]);
+
+$targetIds = $collector->getTargetIds($sources, $path);
+
+$sourcesToTargetIdsMap = $collector->getSourcesToTargetIdsMap($sources, $path);
+$targetIds1 = $sourcesToTargetIdsMap->get([$source1]);
+$targetIds1And2 = $sourcesToTargetIdsMap->get([$source1, $source2]);
+
+// Need to specify strategy for final objects - if they should be fully loaded or if proxies are enough.
+
+$doctrineOrmAssociationPath = $doctrineOrmAssociationPathBuilder
+ ->associate('bar')
+ ->aliasAs('bars')
+ ->loadFull()
+ ->associate('baz')
+ ->aliasAs('bazs')
+ ->create();
+
+$doctrineOrmAssociationPath = $doctrineOrmAssociationPathBuilder
+ ->associate('bar')
+ ->associate('baz')
+ ->create();
+
+// Equivalent to
+$doctrineOrmAssociationPath = new DoctrineOrmAssociationPath([
+ (new DoctrineOrmAssociation('bar'))
+ ->setAlias('bars')
+ ->setLoadMode(DoctrineOrmAssociationLoad::FULL),
+ (new DoctrineOrmAssociation('baz'))
+ ->setLoadMode(DoctrineOrmAssociationLoad::PROXY),
+]);
+
+$doctrineOrmEntitiesSource = (new DoctrineOrmEntitiesSource([$foo1, $foo2, $foo3]))
+ ->declareEntityClass(Foo::class);
+
+$targets = $associate
+ ->getAdapter('doctrine')
+ ->getTargets($doctrineOrmEntitiesSource, $doctrineOrmAssociationPath);
+
+$bazs = $targets->getAll();
+
+// Complete usage for prefetching with simplified facad, diverging paths.
+$associateDoctrineOrmFetcher->fetch(
+ $foos,
+ Foo::class,
+ [
+ ['foo', [
+ ['bar'],
+ ['baz' , 'qux'],
+ ]],
+ ['qux'],
+ ]
+);
+
+
+// .foo.bar
+// .baz.qux
+// .qux
+//
+$doctrineOrmAssociationPathBuilder
+ ->diverge()
+ ->associate('foo')
+ ->diverge()
+ ->associate('bar')
+ ->endDiverge()
+ ->diverge()
+ ->associate('baz')
+ ->associate('qux')
+ ->endDiverge()
+ ->endDiverge()
+ ->diverge()
+ ->associate('qux')
+ ->endDiverge();
+
+// .foo.bar.baz.qux
+// .qux
+//
+$doctrineOrmAssociationPathBuilder
+ ->associate('foo')
+ ->associate('bar')
+ ->diverge()
+ ->associate('baz')
+ ->associate('qux')
+ ->endDiverge()
+ ->diverge()
+ ->associate('qux')
+ ->endDiverge();
diff --git a/draft-2.php b/draft-2.php
new file mode 100644
index 0000000..17544d9
--- /dev/null
+++ b/draft-2.php
@@ -0,0 +1,14 @@
+bar = $bar;
+ }
+
+ /**
+ * @return Bar|null
+ */
+ public function getBar()
+ {
+ return $this->bar;
+ }
+}
+
+class Bar
+{
+ /**
+ * @var Baz[]
+ */
+ public $bazs;
+
+ /**
+ * @param Baz[] $bazs
+ */
+ public function __construct(array $bazs)
+ {
+ $this->bazs = $bazs;
+ }
+}
+
+class Baz
+{
+ /**
+ * @var string
+ */
+ protected $text;
+
+ /**
+ * @param string $text
+ */
+ public function __construct(string $text)
+ {
+ $this->text = $text;
+ }
+
+ /**
+ * @return string
+ */
+ public function getText(): string
+ {
+ return $this->text;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getWords(): array
+ {
+ return explode(' ', $this->text);
+ }
+
+ /**
+ * @return array
+ */
+ public function getTextStats(): array
+ {
+ return ['wordCount' => count($this->getWords())];
+ }
+}
+
+$foos = [
+ $foo1 = new Foo(
+ $bar1 = new Bar([
+ $baz1 = new Baz('lorem ipsum'),
+ $baz2 = new Baz('dolor'),
+ ])
+ ),
+ $foo2 = new Foo(
+ $bar2 = new Bar([
+ $baz1,
+ $baz3 = new Baz('sit amet malef'),
+ $baz4 = new Baz('dolor sit'),
+ ])
+ ),
+ $foo3 = new Foo(),
+];
+
+$facade = new \Xsolve\Associate\Facade();
+$basicCollector = $facade->getBasicCollector();
+
+$bars = $basicCollector->collect($foos, ['bar']);
+var_dump($bars);
+die;
+
+$bazs = $basicCollector->collect($foos, ['bar', 'bazs']);
+var_dump($bazs);
+
+$texts = $basicCollector->collect($foos, ['bar', 'bazs', 'text']);
+var_dump($texts);
+
+$words = $basicCollector->collect($foos, ['bar', 'bazs', 'words']);
+var_dump($words);
+
+$wordCounts = $basicCollector->collect($foos, ['bar', 'bazs', 'textStats', '[wordCount]']);
+var_dump($wordCounts);
diff --git a/lib/AssociationCollecting/AssociationCollectingStrategyInterface.php b/legacyLib/AssociationCollecting/AssociationCollectingStrategyInterface.php
similarity index 81%
rename from lib/AssociationCollecting/AssociationCollectingStrategyInterface.php
rename to legacyLib/AssociationCollecting/AssociationCollectingStrategyInterface.php
index e615d4b..fb32c4e 100644
--- a/lib/AssociationCollecting/AssociationCollectingStrategyInterface.php
+++ b/legacyLib/AssociationCollecting/AssociationCollectingStrategyInterface.php
@@ -1,6 +1,6 @@
classMetadataWrappers)) {
- $classMetadata = $this->entityManager->getClassMetadata($className);
-
- if ($classMetadata instanceof ClassMetadata) {
- $entityRepository = $this->entityManager->getRepository($className);
- if (!$entityRepository instanceof EntityRepository) {
- throw new \Exception();
- }
- $this->classMetadataWrappers[$className] = new ClassMetadataWrapper($this, $entityRepository, $classMetadata);
- } else {
- $this->classMetadataWrappers[$className] = null;
- }
+ $this->initializeClassMetadataWrapperByClassName($className);
}
return $this->classMetadataWrappers[$className];
}
+
+ /**
+ * @param string $className
+ *
+ * @throws \Exception
+ */
+ protected function initializeClassMetadataWrapperByClassName(string $className): void
+ {
+ $classMetadata = $this->entityManager->getClassMetadata($className);
+ if (!$classMetadata instanceof ClassMetadata) {
+ $this->classMetadataWrappers[$className] = null;
+
+ return;
+ }
+
+ $entityRepository = $this->entityManager->getRepository($className);
+ if (!$entityRepository instanceof EntityRepository) {
+ throw new \Exception();
+ }
+ $this->classMetadataWrappers[$className] = new ClassMetadataWrapper($this, $entityRepository, $classMetadata);
+ }
}
diff --git a/lib/ObjectCollection/NonObjectWrapper.php b/legacyLib/ObjectCollection/NonObjectWrapper.php
similarity index 87%
rename from lib/ObjectCollection/NonObjectWrapper.php
rename to legacyLib/ObjectCollection/NonObjectWrapper.php
index e7d0140..fb8b264 100644
--- a/lib/ObjectCollection/NonObjectWrapper.php
+++ b/legacyLib/ObjectCollection/NonObjectWrapper.php
@@ -1,6 +1,6 @@
setName($categoryData['name']);
+ $this->categoriesMap[$category->getId()] = $category;
+ }
+
+ foreach ($data['users'] as $userData) {
+ $user = new User($userData['id']);
+ foreach ($userData['stores'] as $storeData) {
+ $store = new Store($storeData['id']);
+ $store->setUser($user);
+ $user->addStore($store);
+ $store->setApproved($storeData['approved']);
+ foreach ($storeData['products'] as $productData) {
+ $product = new Product($productData['id']);
+ $product->setStore($store);
+ $store->addProduct($product);
+ $product->setApproved($productData['approved']);
+ $product->setName($productData['name']);
+ $product->setCategory($this->categoriesMap[$productData['categoryId']]);
+ foreach ($productData['variants'] as $variantData) {
+ $variant = new Variant($variantData['id']);
+ $variant->setProduct($product);
+ $product->addVariant($variant);
+ $variant->setPrice($variantData['price']);
+ $this->variantsMap[$variant->getId()] = $variant;
+ }
+ $this->productsMap[$product->getId()] = $product;
+ }
+ $this->storesMap[$store->getId()] = $store;
+ }
+ $this->usersMap[$user->getId()] = $user;
+ }
+ }
+
+ /**
+ * @return User[]
+ */
+ public function getUsers(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->usersMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->usersMap);
+ }
+
+ /**
+ * @return Store[]
+ */
+ public function getStores(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->storesMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->storesMap);
+ }
+
+ /**
+ * @return Product[]
+ */
+ public function getProducts(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->productsMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->productsMap);
+ }
+
+ /**
+ * @return Variant[]
+ */
+ public function getVariants(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->variantsMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->variantsMap);
+ }
+
+ /**
+ * @return Category[]
+ */
+ public function getCategories(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->categoriesMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->categoriesMap);
+ }
+
+ /**
+ * @return array
+ */
+ public function getAll(): array
+ {
+ return array_merge(
+ $this->getCategories(),
+ $this->getUsers(),
+ $this->getStores(),
+ $this->getProducts(),
+ $this->getVariants()
+ );
+ }
+}
diff --git a/legacyTests/Functional/DoctrineOrm/Mock/Entity/Category.php b/legacyTests/Functional/DoctrineOrm/Mock/Entity/Category.php
new file mode 100644
index 0000000..ec8a942
--- /dev/null
+++ b/legacyTests/Functional/DoctrineOrm/Mock/Entity/Category.php
@@ -0,0 +1,93 @@
+id = $id;
+
+ $this->products = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param Product[] $products
+ */
+ public function setProducts(array $products)
+ {
+ $this->products->clear();
+ foreach ($products as $product) {
+ $this->products->add($product);
+ }
+ }
+
+ /**
+ * @return Collection|Product[]
+ */
+ public function getProducts(): Collection
+ {
+ return $this->products;
+ }
+}
diff --git a/legacyTests/Functional/DoctrineOrm/Mock/Entity/Product.php b/legacyTests/Functional/DoctrineOrm/Mock/Entity/Product.php
new file mode 100644
index 0000000..eb29752
--- /dev/null
+++ b/legacyTests/Functional/DoctrineOrm/Mock/Entity/Product.php
@@ -0,0 +1,171 @@
+id = $id;
+
+ $this->variants = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param bool $approved
+ */
+ public function setApproved(bool $approved)
+ {
+ $this->approved = $approved;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isApproved(): bool
+ {
+ return $this->approved;
+ }
+
+ /**
+ * @param Store $store
+ */
+ public function setStore(Store $store)
+ {
+ $this->store = $store;
+ }
+
+ /**
+ * @return Store
+ */
+ public function getStore(): Store
+ {
+ return $this->store;
+ }
+
+ /**
+ * @param Category $category
+ */
+ public function setCategory(Category $category)
+ {
+ $this->category = $category;
+ }
+
+ /**
+ * @return Category
+ */
+ public function getCategory(): Category
+ {
+ return $this->category;
+ }
+
+ /**
+ * @param Variant[] $variants
+ */
+ public function setVariants(array $variants)
+ {
+ $this->variants->clear();
+ foreach ($variants as $variant) {
+ $this->variants->add($variant);
+ }
+ }
+
+ /**
+ * @param Variant $variant
+ */
+ public function addVariant(Variant $variant)
+ {
+ $this->variants->add($variant);
+ }
+
+ /**
+ * @return Collection|Variant[]
+ */
+ public function getVariants()
+ {
+ return $this->variants;
+ }
+}
diff --git a/legacyTests/Functional/DoctrineOrm/Mock/Entity/Store.php b/legacyTests/Functional/DoctrineOrm/Mock/Entity/Store.php
new file mode 100644
index 0000000..c9ed7ef
--- /dev/null
+++ b/legacyTests/Functional/DoctrineOrm/Mock/Entity/Store.php
@@ -0,0 +1,148 @@
+id = $id;
+
+ $this->products = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+// /**
+// * @param string $name
+// */
+// public function setName(string $name)
+// {
+// $this->name = $name;
+// }
+//
+// /**
+// * @return string
+// */
+// public function getName(): string
+// {
+// return $this->name;
+// }
+
+ /**
+ * @param bool $approved
+ */
+ public function setApproved(bool $approved)
+ {
+ $this->approved = $approved;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isApproved(): bool
+ {
+ return $this->approved;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ }
+
+ /**
+ * @return User
+ */
+ public function getUser(): User
+ {
+ return $this->user;
+ }
+
+ /**
+ * @param Product[] $products
+ */
+ public function setProducts(array $products)
+ {
+ $this->products->clear();
+ foreach ($products as $product) {
+ $this->products->add($product);
+ }
+ }
+
+ /**
+ * @param Product $product
+ */
+ public function addProduct(Product $product)
+ {
+ $this->products->add($product);
+ }
+
+ /**
+ * @return Collection|Product[]
+ */
+ public function getProducts(): Collection
+ {
+ return $this->products;
+ }
+}
diff --git a/legacyTests/Functional/DoctrineOrm/Mock/Entity/User.php b/legacyTests/Functional/DoctrineOrm/Mock/Entity/User.php
new file mode 100644
index 0000000..65b7937
--- /dev/null
+++ b/legacyTests/Functional/DoctrineOrm/Mock/Entity/User.php
@@ -0,0 +1,78 @@
+id = $id;
+
+ $this->stores = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param Store[] $stores
+ */
+ public function setStores(array $stores)
+ {
+ $this->stores->clear();
+ foreach ($stores as $store) {
+ $this->stores->add($store);
+ }
+ }
+
+ /**
+ * @param Store $store
+ */
+ public function addStore(Store $store)
+ {
+ $this->stores->add($store);
+ }
+
+ /**
+ * @return Collection|Store[]
+ */
+ public function getStores(): Collection
+ {
+ return $this->stores;
+ }
+}
diff --git a/legacyTests/Functional/DoctrineOrm/Mock/Entity/Variant.php b/legacyTests/Functional/DoctrineOrm/Mock/Entity/Variant.php
new file mode 100644
index 0000000..7a92683
--- /dev/null
+++ b/legacyTests/Functional/DoctrineOrm/Mock/Entity/Variant.php
@@ -0,0 +1,86 @@
+id = $id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param int $price
+ */
+ public function setPrice(int $price)
+ {
+ $this->price = $price;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPrice(): int
+ {
+ return $this->price;
+ }
+
+ /**
+ * @param Product $product
+ */
+ public function setProduct(Product $product)
+ {
+ $this->product = $product;
+ }
+
+ /**
+ * @return Product
+ */
+ public function getProduct(): Product
+ {
+ return $this->product;
+ }
+}
diff --git a/legacyTests/Functional/DoctrineOrmHelper.php b/legacyTests/Functional/DoctrineOrmHelper.php
new file mode 100644
index 0000000..307c357
--- /dev/null
+++ b/legacyTests/Functional/DoctrineOrmHelper.php
@@ -0,0 +1,93 @@
+ 'pdo_sqlite',
+ 'dbname' => 'sifter_test', // TODO Move to some config file.
+ 'user' => 'root', // TODO Move to some config file.
+ 'password' => '', // TODO Move to some config file.
+ 'memory' => true, // TODO Move to some config file.
+ ];
+
+ $metadataConfiguration = Setup::createAnnotationMetadataConfiguration($entityPaths, $isDevMode);
+ $this->entityManager = EntityManager::create($databaseOptions, $metadataConfiguration);
+ }
+
+ /**
+ * @return EntityManagerInterface
+ */
+ public function getEntityManager(): EntityManagerInterface
+ {
+ if (!$this->entityManager instanceof EntityManagerInterface) {
+ $this->setupEntityManager();
+ }
+
+ return $this->entityManager;
+ }
+
+ /**
+ * @throws \Doctrine\ORM\Tools\ToolsException
+ */
+ public function createSchema()
+ {
+ $this->getEntityManager();
+
+ $entityClassMetadatas = [];
+ foreach ($this->getEntityClassNames() as $entityClassName) {
+ $entityClassMetadatas[] = $this->entityManager->getClassMetadata($entityClassName);
+ }
+
+ $tool = new SchemaTool($this->entityManager);
+ $tool->createSchema($entityClassMetadatas);
+ }
+
+ public function loadData()
+ {
+ $this->getEntityManager();
+
+ $dataProvider = new DataProvider();
+ foreach ($dataProvider->getAll() as $entity) {
+ $this->entityManager->persist($entity);
+ }
+ $this->entityManager->flush();
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getEntityClassNames(): array
+ {
+ return [
+ Category::class,
+ User::class,
+ Store::class,
+ Product::class,
+ Variant::class,
+ ];
+ }
+}
diff --git a/legacyTests/Functional/GenericEntity/Mock/DataProvider.php b/legacyTests/Functional/GenericEntity/Mock/DataProvider.php
new file mode 100644
index 0000000..07c7f51
--- /dev/null
+++ b/legacyTests/Functional/GenericEntity/Mock/DataProvider.php
@@ -0,0 +1,185 @@
+setId($categoryData['id']);
+ $category->setName($categoryData['name']);
+ $this->categoriesMap[$category->getId()] = $category;
+ }
+
+ foreach ($data['users'] as $userData) {
+ $user = new User();
+ $user->setId($userData['id']);
+ foreach ($userData['stores'] as $storeData) {
+ $store = new Store();
+ $store->setUser($user);
+ $user->addStore($store);
+ $store->setId($storeData['id']);
+ $store->setApproved($storeData['approved']);
+ foreach ($storeData['products'] as $productData) {
+ $product = new Product();
+ $product->setStore($store);
+ $store->addProduct($product);
+ $product->setId($productData['id']);
+ $product->setApproved($productData['approved']);
+ $product->setName($productData['name']);
+ $product->setCategory($this->categoriesMap[$productData['categoryId']]);
+ foreach ($productData['variants'] as $variantData) {
+ $variant = new Variant();
+ $variant->setProduct($product);
+ $product->addVariant($variant);
+ $variant->setId($variantData['id']);
+ $variant->setPrice($variantData['price']);
+ $this->variantsMap[$variant->getId()] = $variant;
+ }
+ $this->productsMap[$product->getId()] = $product;
+ }
+ $this->storesMap[$store->getId()] = $store;
+ }
+ $this->usersMap[$user->getId()] = $user;
+ }
+ }
+
+ /**
+ * @return User[]
+ */
+ public function getUsers(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->usersMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->usersMap);
+ }
+
+ /**
+ * @return Store[]
+ */
+ public function getStores(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->storesMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->storesMap);
+ }
+
+ /**
+ * @return Product[]
+ */
+ public function getProducts(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->productsMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->productsMap);
+ }
+
+ /**
+ * @return Variant[]
+ */
+ public function getVariants(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->variantsMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->variantsMap);
+ }
+
+ /**
+ * @return Category[]
+ */
+ public function getCategories(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->categoriesMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->categoriesMap);
+ }
+
+ /**
+ * @return array
+ */
+ public function getAll(): array
+ {
+ return array_merge(
+ $this->getCategories(),
+ $this->getUsers(),
+ $this->getStores(),
+ $this->getProducts(),
+ $this->getVariants()
+ );
+ }
+}
diff --git a/legacyTests/Functional/GenericEntity/Mock/Model/Category.php b/legacyTests/Functional/GenericEntity/Mock/Model/Category.php
new file mode 100644
index 0000000..7383b72
--- /dev/null
+++ b/legacyTests/Functional/GenericEntity/Mock/Model/Category.php
@@ -0,0 +1,48 @@
+id;
+ }
+
+ /**
+ * @param string $id
+ */
+ public function setId(string $id)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+}
diff --git a/legacyTests/Functional/GenericEntity/Mock/Model/Product.php b/legacyTests/Functional/GenericEntity/Mock/Model/Product.php
new file mode 100644
index 0000000..ebe885a
--- /dev/null
+++ b/legacyTests/Functional/GenericEntity/Mock/Model/Product.php
@@ -0,0 +1,132 @@
+store;
+ }
+
+ /**
+ * @param Store $store
+ */
+ public function setStore(Store $store)
+ {
+ $this->store = $store;
+ }
+
+ /**
+ * @return Variant[]
+ */
+ public function getVariants(): array
+ {
+ return $this->variants;
+ }
+
+ /**
+ * @param Variant $variant
+ */
+ public function addVariant(Variant $variant)
+ {
+ $this->variants[] = $variant;
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param string $id
+ */
+ public function setId(string $id)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isApproved(): bool
+ {
+ return $this->approved;
+ }
+
+ /**
+ * @param bool $approved
+ */
+ public function setApproved(bool $approved)
+ {
+ $this->approved = $approved;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return Category
+ */
+ public function getCategory(): Category
+ {
+ return $this->category;
+ }
+
+ /**
+ * @param Category $category
+ */
+ public function setCategory(Category $category)
+ {
+ $this->category = $category;
+ }
+}
diff --git a/legacyTests/Functional/GenericEntity/Mock/Model/Store.php b/legacyTests/Functional/GenericEntity/Mock/Model/Store.php
new file mode 100644
index 0000000..13f4d88
--- /dev/null
+++ b/legacyTests/Functional/GenericEntity/Mock/Model/Store.php
@@ -0,0 +1,90 @@
+user;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ }
+
+ /**
+ * @return Product[]
+ */
+ public function getProducts(): array
+ {
+ return $this->products;
+ }
+
+ /**
+ * @param Product $product
+ */
+ public function addProduct(Product $product)
+ {
+ $this->products[] = $product;
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param string $id
+ */
+ public function setId(string $id)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isApproved(): bool
+ {
+ return $this->approved;
+ }
+
+ /**
+ * @param bool $approved
+ */
+ public function setApproved(bool $approved)
+ {
+ $this->approved = $approved;
+ }
+}
diff --git a/legacyTests/Functional/GenericEntity/Mock/Model/User.php b/legacyTests/Functional/GenericEntity/Mock/Model/User.php
new file mode 100644
index 0000000..13232fa
--- /dev/null
+++ b/legacyTests/Functional/GenericEntity/Mock/Model/User.php
@@ -0,0 +1,48 @@
+id;
+ }
+
+ /**
+ * @param string $id
+ */
+ public function setId(string $id)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @param Store $store
+ */
+ public function addStore(Store $store)
+ {
+ $this->stores[] = $store;
+ }
+
+ /**
+ * @return Store[]
+ */
+ public function getStores(): array
+ {
+ return $this->stores;
+ }
+}
diff --git a/legacyTests/Functional/GenericEntity/Mock/Model/Variant.php b/legacyTests/Functional/GenericEntity/Mock/Model/Variant.php
new file mode 100644
index 0000000..020f502
--- /dev/null
+++ b/legacyTests/Functional/GenericEntity/Mock/Model/Variant.php
@@ -0,0 +1,69 @@
+id;
+ }
+
+ /**
+ * @param string $id
+ */
+ public function setId(string $id)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @return Product
+ */
+ public function getProduct(): Product
+ {
+ return $this->product;
+ }
+
+ /**
+ * @param Product $product
+ */
+ public function setProduct(Product $product)
+ {
+ $this->product = $product;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPrice(): int
+ {
+ return $this->price;
+ }
+
+ /**
+ * @param int $price
+ */
+ public function setPrice(int $price)
+ {
+ $this->price = $price;
+ }
+}
diff --git a/tests/Functional/Model/Bar.php b/legacyTests/Functional/Model/Bar.php
similarity index 78%
rename from tests/Functional/Model/Bar.php
rename to legacyTests/Functional/Model/Bar.php
index 445604d..ecb8e08 100644
--- a/tests/Functional/Model/Bar.php
+++ b/legacyTests/Functional/Model/Bar.php
@@ -1,6 +1,6 @@
relationshipName = $relationshipName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRelationshipName(): string
+ {
+ return $this->relationshipName;
+ }
+
+ /**
+ * @param mixed $alias
+ *
+ * @return self
+ */
+ public function setAlias($alias): self
+ {
+ $this->alias = $alias;
+
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getAlias()
+ {
+ return $this->alias;
+ }
+
+ /**
+ * @param string $loadMode
+ *
+ * @return self
+ */
+ public function setLoadMode(string $loadMode): self
+ {
+ $this->loadMode = $loadMode;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isLoadModeFull(): bool
+ {
+ return self::LOAD_MODE_FULL === $this->loadMode;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isLoadModeProxy(): bool
+ {
+ return self::LOAD_MODE_PROXY === $this->loadMode;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isLoadModeNone(): bool
+ {
+ return self::LOAD_MODE_NONE === $this->loadMode;
+ }
+}
diff --git a/lib/DoctrineOrm/Association/Path/AssociationPath.php b/lib/DoctrineOrm/Association/Path/AssociationPath.php
new file mode 100644
index 0000000..6bc7857
--- /dev/null
+++ b/lib/DoctrineOrm/Association/Path/AssociationPath.php
@@ -0,0 +1,27 @@
+associations[] = $association;
+ }
+
+ /**
+ * @return Association[]
+ */
+ public function getAssociations(): array
+ {
+ return $this->associations;
+ }
+}
diff --git a/lib/DoctrineOrm/Association/Path/Builder/AssociationBuilder.php b/lib/DoctrineOrm/Association/Path/Builder/AssociationBuilder.php
new file mode 100644
index 0000000..b10268d
--- /dev/null
+++ b/lib/DoctrineOrm/Association/Path/Builder/AssociationBuilder.php
@@ -0,0 +1,109 @@
+association = $association;
+ $this->parentBuilder = $parentBuilder;
+ }
+
+ /**
+ * @param string $alias
+ *
+ * @return self
+ */
+ public function aliasAs(string $alias): self
+ {
+ $this->association->setAlias($alias);
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ public function loadFull(): self
+ {
+ $this->association->setLoadMode(Association::LOAD_MODE_FULL);
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ public function loadProxy(): self
+ {
+ $this->association->setLoadMode(Association::LOAD_MODE_PROXY);
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ public function loadNone(): self
+ {
+ $this->association->setLoadMode(Association::LOAD_MODE_NONE);
+
+ return $this;
+ }
+
+ /**
+ * @param string $relationshipName
+ *
+ * @return self
+ *
+ * @throws \Exception
+ */
+ public function associate(string $relationshipName): self
+ {
+ return $this->parentBuilder->associate($relationshipName);
+ }
+
+ /**
+ * @return DivergingAssociationPathBuilder
+ */
+ public function diverge(): DivergingAssociationPathBuilder
+ {
+ return $this->parentBuilder->diverge();
+ }
+
+ /**
+ * @return DivergingAssociationPathBuilder
+ *
+ * @throws \Exception
+ */
+ public function endDiverge(): DivergingAssociationPathBuilder
+ {
+ return $this->parentBuilder->endDiverge();
+ }
+
+ /**
+ * @return DivergingAssociationPath
+ */
+ public function create(): DivergingAssociationPath
+ {
+ return $this->parentBuilder->create();
+ }
+}
diff --git a/lib/DoctrineOrm/Association/Path/Builder/DivergingAssociationPathBuilder.php b/lib/DoctrineOrm/Association/Path/Builder/DivergingAssociationPathBuilder.php
new file mode 100644
index 0000000..1469979
--- /dev/null
+++ b/lib/DoctrineOrm/Association/Path/Builder/DivergingAssociationPathBuilder.php
@@ -0,0 +1,92 @@
+divergingAssociationPath = $divergingAssociationPath instanceof DivergingAssociationPath
+ ? $divergingAssociationPath
+ : new DivergingAssociationPath();
+ $this->parentBuilder = $parentBuilder;
+ }
+
+ /**
+ * @param string $relationshipName
+ *
+ * @return AssociationBuilder
+ *
+ * @throws \Exception
+ */
+ public function associate(string $relationshipName): AssociationBuilder
+ {
+ if ($this->divergingAssociationPath->hasChildDivergingAssociationPath()) {
+ throw new \Exception();
+ }
+
+ $association = new Association($relationshipName);
+ $this->divergingAssociationPath->addAssociation($association);
+
+ return new AssociationBuilder($association, $this);
+ }
+
+ /**
+ * @return self
+ */
+ public function diverge(): self
+ {
+ $childDivergingAssociationPath = new DivergingAssociationPath();
+ $this->divergingAssociationPath->addChildDivergingAsociationPath($childDivergingAssociationPath);
+
+ return new self($childDivergingAssociationPath, $this);
+ }
+
+ /**
+ * @return self
+ *
+ * @throws \Exception
+ */
+ public function endDiverge(): self
+ {
+ if (!$this->parentBuilder instanceof self) {
+ throw new \Exception();
+ }
+
+ return $this->parentBuilder;
+ }
+
+ /**
+ * @return DivergingAssociationPath
+ */
+ public function create(): DivergingAssociationPath
+ {
+ if ($this->parentBuilder instanceof self) {
+ return $this->parentBuilder->create();
+ }
+
+ return $this->divergingAssociationPath;
+ }
+}
diff --git a/lib/DoctrineOrm/Association/Path/DivergingAssociationPath.php b/lib/DoctrineOrm/Association/Path/DivergingAssociationPath.php
new file mode 100644
index 0000000..ca23588
--- /dev/null
+++ b/lib/DoctrineOrm/Association/Path/DivergingAssociationPath.php
@@ -0,0 +1,35 @@
+childDivergingAssociationPaths[] = $childDivergingAssociationPath;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasChildDivergingAssociationPath(): bool
+ {
+ return (bool) $this->childDivergingAssociationPaths;
+ }
+
+ /**
+ * @return self[]
+ */
+ public function getChildDivergingAssociationPaths(): array
+ {
+ return $this->childDivergingAssociationPaths;
+ }
+}
diff --git a/lib/DoctrineOrm/Metadata/AssociationMetadataAdapter.php b/lib/DoctrineOrm/Metadata/AssociationMetadataAdapter.php
new file mode 100644
index 0000000..11921ac
--- /dev/null
+++ b/lib/DoctrineOrm/Metadata/AssociationMetadataAdapter.php
@@ -0,0 +1,132 @@
+metadataAdapterProvider = $metadataAdapterProvider;
+ $this->associationMapping = $associationMapping;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->associationMapping['fieldName'];
+ }
+
+ /**
+ * @return bool
+ */
+ public function isOwningSide(): bool
+ {
+ return $this->associationMapping['isOwningSide'];
+ }
+
+ /**
+ * @return bool
+ */
+ public function isInverseSide(): bool
+ {
+ return !$this->isOwningSide();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isOneToOne(): bool
+ {
+ return ClassMetadataInfo::ONE_TO_ONE === $this->associationMapping['type'];
+ }
+
+ /**
+ * @return bool
+ */
+ public function isOneToMany(): bool
+ {
+ return ClassMetadataInfo::ONE_TO_MANY === $this->associationMapping['type'];
+ }
+
+ /**
+ * @return bool
+ */
+ public function isManyToOne(): bool
+ {
+ return ClassMetadataInfo::MANY_TO_ONE === $this->associationMapping['type'];
+ }
+
+ /**
+ * @return bool
+ */
+ public function isManyToMany(): bool
+ {
+ return ClassMetadataInfo::MANY_TO_MANY === $this->associationMapping['type'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getSourceClassName(): string
+ {
+ return $this->associationMapping['sourceEntity'];
+ }
+
+ /**
+ * @return ClassMetadataAdapter
+ *
+ * @throws \Exception
+ */
+ public function getSourceClassMetadataAdapter(): ClassMetadataAdapter
+ {
+ $sourceClassName = $this->metadataAdapterProvider->getClassMetadataAdapterByClassName($this->getSourceClassName());
+ if (!$sourceClassName instanceof ClassMetadataAdapter) {
+ throw new \Exception('Source class name not determined.');
+ }
+
+ return $sourceClassName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTargetClassName(): string
+ {
+ return $this->associationMapping['targetEntity'];
+ }
+
+ /**
+ * @return ClassMetadataAdapter
+ *
+ * @throws \Exception
+ */
+ public function getTargetClassMetadataAdapter(): ClassMetadataAdapter
+ {
+ $targetClassName = $this->metadataAdapterProvider->getClassMetadataAdapterByClassName($this->getTargetClassName());
+ if (!$targetClassName instanceof ClassMetadataAdapter) {
+ throw new \Exception('Target class name not determined.');
+ }
+
+ return $targetClassName;
+ }
+}
diff --git a/lib/DoctrineOrm/Metadata/ClassMetadataAdapter.php b/lib/DoctrineOrm/Metadata/ClassMetadataAdapter.php
new file mode 100644
index 0000000..0806ade
--- /dev/null
+++ b/lib/DoctrineOrm/Metadata/ClassMetadataAdapter.php
@@ -0,0 +1,153 @@
+metadataAdapterProvider = $metadataAdapterProvider;
+ $this->repository = $repository;
+ $this->classMetadata = $classMetadata;
+ }
+
+ /**
+ * @return string
+ */
+ public function getClassName(): string
+ {
+ return $this->classMetadata->getName();
+ }
+
+ /**
+ * @return string
+ */
+ public function getRootClassName(): string
+ {
+ return $this->classMetadata->rootEntityName;
+ }
+
+ /**
+ * @param string $rootAlias
+ *
+ * @return QueryBuilder
+ */
+ public function createQueryBuilder(string $rootAlias): QueryBuilder
+ {
+ return $this->repository->createQueryBuilder($rootAlias);
+ }
+
+ /**
+ * @param object $object
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function getIdentifierValueForOne(object $object)
+ {
+ $identifierValues = $this->classMetadata->getIdentifierValues($object);
+
+ return $identifierValues[$this->getIdentifierFieldName()];
+ }
+
+ /**
+ * @param object[] $objects
+ *
+ * @return array
+ */
+ public function getIdentifierValueForMultiple(array $objects): array
+ {
+ return array_map(
+ function (object $object) {
+ return $this->getIdentifierValueForOne($object);
+ },
+ $objects
+ );
+ }
+
+ /**
+ * @param string $associationName
+ *
+ * @return AssociationMetadataAdapter|null
+ *
+ * @throws MappingException
+ */
+ public function getAssociationMetadataAdapter(string $associationName): ?AssociationMetadataAdapter
+ {
+ if (!array_key_exists($associationName, $this->associationMetadataAdapters)) {
+ try {
+ $associationMapping = $this->classMetadata->getAssociationMapping($associationName);
+ } catch (MappingException $e) {
+ if (0 === strpos($e->getMessage(), 'No mapping found for field ')) {
+ return null;
+ }
+ throw $e;
+ }
+ $this->associationMetadataAdapters[$associationName] = new AssociationMetadataAdapter(
+ $this->metadataAdapterProvider,
+ $associationMapping
+ );
+ }
+
+ return $this->associationMetadataAdapters[$associationName];
+ }
+
+ /**
+ * @return string
+ *
+ * @throws \Exception
+ */
+ public function getIdentifierFieldName(): string
+ {
+ if (is_null($this->identifierFieldName)) {
+ $identifierFieldNames = $this->classMetadata->getIdentifierFieldNames();
+
+ if (1 !== count($identifierFieldNames)) {
+ throw new \Exception('Composite primary keys are not supported.');
+ }
+
+ $this->identifierFieldName = reset($identifierFieldNames);
+ }
+
+ return $this->identifierFieldName;
+ }
+}
diff --git a/lib/DoctrineOrm/Metadata/MetadataAdapterProvider.php b/lib/DoctrineOrm/Metadata/MetadataAdapterProvider.php
new file mode 100644
index 0000000..864b500
--- /dev/null
+++ b/lib/DoctrineOrm/Metadata/MetadataAdapterProvider.php
@@ -0,0 +1,120 @@
+entityManager = $entityManager;
+ }
+
+// /**
+// * @param array $objects
+// *
+// * @return ClassMetadataAdapter
+// *
+// * @throws \Exception
+// */
+// public function getClassMetadataAdapterForEntities(array $objects): ClassMetadataAdapter
+// {
+// $className = $this->getClassNameForEntities($objects);
+// $classMetadataAdapter = $this->getClassMetadataAdapterByClassName($className);
+// if (!$classMetadataAdapter instanceof ClassMetadataAdapter) {
+// throw new \Exception();
+// }
+//
+// return $classMetadataAdapter;
+// }
+
+ /**
+ * @param array $objects
+ *
+ * @return string
+ *
+ * @throws \Exception
+ */
+ public function getClassNameForEntities(array $objects): string
+ {
+ if (!$objects) {
+ throw new \Exception();
+ }
+
+ $firstObject = array_shift($objects);
+ $classMetadataAdapter = $this->getClassMetadataAdapterByClassName(get_class($firstObject));
+ if (!$classMetadataAdapter instanceof ClassMetadataAdapter) {
+ throw new \Exception();
+ }
+ $commonClassName = $classMetadataAdapter->getClassName();
+ $rootClassNameUsed = false;
+
+ foreach ($objects as $object) {
+ if (is_a($object, $commonClassName, true)) {
+ continue;
+ }
+ if ($rootClassNameUsed) {
+ throw new \Exception();
+ }
+ $commonClassName = $classMetadataAdapter->getRootClassName();
+ if (!is_a($object, $commonClassName, true)) {
+ throw new \Exception();
+ }
+ }
+
+ return $commonClassName;
+ }
+
+ /**
+ * @param string $className
+ *
+ * @return ClassMetadataAdapter|null
+ *
+ * @throws \Exception
+ */
+ public function getClassMetadataAdapterByClassName(string $className): ?ClassMetadataAdapter
+ {
+ if (!array_key_exists($className, $this->classMetadataAdapters)) {
+ $this->initializeClassMetadataAdapterByClassName($className);
+ }
+
+ return $this->classMetadataAdapters[$className];
+ }
+
+ /**
+ * @param string $className
+ *
+ * @throws \Exception
+ */
+ protected function initializeClassMetadataAdapterByClassName(string $className): void
+ {
+ $classMetadata = $this->entityManager->getClassMetadata($className);
+ if (!$classMetadata instanceof ClassMetadata) {
+ $this->classMetadataAdapters[$className] = null;
+
+ return;
+ }
+
+ $entityRepository = $this->entityManager->getRepository($className);
+ if (!$entityRepository instanceof EntityRepository) {
+ throw new \Exception();
+ }
+ $this->classMetadataAdapters[$className] = new ClassMetadataAdapter($this, $entityRepository, $classMetadata);
+ }
+}
diff --git a/lib/DoctrineOrm/Source/EntityClassHelper.php b/lib/DoctrineOrm/Source/EntityClassHelper.php
new file mode 100644
index 0000000..d70ea3e
--- /dev/null
+++ b/lib/DoctrineOrm/Source/EntityClassHelper.php
@@ -0,0 +1,43 @@
+metadataAdapterProvider = $metadataAdapterProvider;
+ }
+
+ /**
+ * @param EntitySource $entitySource
+ *
+ * @throws \Exception if one common entity class cannot be determined
+ */
+ public function supplementEntitySource(EntitySource $entitySource): void
+ {
+ if ($entitySource->hasEntityClass()) {
+ return;
+ }
+
+ $entityClassName = $this->metadataAdapterProvider->getClassNameForEntities($entitySource->getEntities());
+ $entitySource->setEntityClass($entityClassName);
+
+ $classMetadataAdapter = $this->metadataAdapterProvider->getClassMetadataAdapterByClassName($entityClassName);
+ if (!$classMetadataAdapter instanceof ClassMetadataAdapter) {
+ throw new \Exception();
+ }
+ $entitySource->setClassMetadataAdapter($classMetadataAdapter);
+ }
+}
diff --git a/lib/DoctrineOrm/Source/EntitySource.php b/lib/DoctrineOrm/Source/EntitySource.php
new file mode 100644
index 0000000..4993d5c
--- /dev/null
+++ b/lib/DoctrineOrm/Source/EntitySource.php
@@ -0,0 +1,81 @@
+entities = $entities;
+ $this->entityClass = $declaredEntityClass;
+ }
+
+ /**
+ * @return array
+ */
+ public function getEntities(): array
+ {
+ return $this->entities;
+ }
+
+ /**
+ * @param string $entityClass
+ */
+ public function setEntityClass(string $entityClass): void
+ {
+ $this->entityClass = $entityClass;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasEntityClass(): bool
+ {
+ return null !== $this->entityClass;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getEntityClass(): ?string
+ {
+ return $this->entityClass;
+ }
+
+ /**
+ * @param ClassMetadataAdapter $classMetadataAdapter
+ */
+ public function setClassMetadataAdapter(ClassMetadataAdapter $classMetadataAdapter): void
+ {
+ $this->classMetadataAdapter = $classMetadataAdapter;
+ }
+
+ /**
+ * @return ClassMetadataAdapter|null
+ */
+ public function getClassMetadataAdapter(): ?ClassMetadataAdapter
+ {
+ return $this->classMetadataAdapter;
+ }
+}
diff --git a/lib/DoctrineOrm/Source/IdentifierSource.php b/lib/DoctrineOrm/Source/IdentifierSource.php
new file mode 100644
index 0000000..d0d3084
--- /dev/null
+++ b/lib/DoctrineOrm/Source/IdentifierSource.php
@@ -0,0 +1,42 @@
+identifiers = $identifiers;
+ $this->entityClass = $entityClass;
+ }
+
+ /**
+ * @return array
+ */
+ public function getIdentifiers(): array
+ {
+ return $this->identifiers;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEntityClass(): string
+ {
+ return $this->entityClass;
+ }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 62f2553..f36331a 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -12,6 +12,7 @@
./
./tests
+ ./legacyTests
./vendor
diff --git a/sandbox-1.php b/sandbox-1.php
new file mode 100755
index 0000000..c1fca00
--- /dev/null
+++ b/sandbox-1.php
@@ -0,0 +1,75 @@
+#!/usr/bin/php
+associate('bar')
+ ->aliasAs('bars')
+ ->loadFull()
+ ->associate('baz')
+ ->associate('qux')
+ ->aliasAs('qux')
+ ->loadProxy()
+ ->create();
+
+dump($doctrineOrmAssociationPath);
+
+$doctrineOrmAssociationPathBuilder = new DivergingAssociationPathBuilder();
+
+$doctrineOrmAssociationPath = $doctrineOrmAssociationPathBuilder
+ ->associate('bar')
+ ->associate('baz')
+ ->associate('qux')
+ ->loadFull()
+ ->create();
+
+dump($doctrineOrmAssociationPath);
+
+// .foo.bar
+// .baz.qux
+// .qux
+//
+$doctrineOrmAssociationPathBuilder = new DivergingAssociationPathBuilder();
+
+$doctrineOrmAssociationPath = $doctrineOrmAssociationPathBuilder
+ ->diverge()
+ ->associate('foo')
+ ->diverge()
+ ->associate('bar')
+ ->endDiverge()
+ ->diverge()
+ ->associate('baz')
+ ->aliasAs('.foo.baz')
+ ->loadProxy()
+ ->associate('qux')
+ ->endDiverge()
+ ->endDiverge()
+ ->diverge()
+ ->associate('qux')
+ ->endDiverge()
+ ->create();
+
+dump($doctrineOrmAssociationPath);
+
+// .foo.bar.baz.qux
+// .qux
+//
+$doctrineOrmAssociationPathBuilder = new DivergingAssociationPathBuilder();
+
+$doctrineOrmAssociationPath = $doctrineOrmAssociationPathBuilder
+ ->associate('foo')
+ ->associate('bar')
+ ->diverge()
+ ->associate('baz')
+ ->associate('qux')
+ ->endDiverge()
+ ->diverge()
+ ->associate('qux')
+ ->endDiverge();
+
+dump($doctrineOrmAssociationPath);
diff --git a/tests/Functional/DoctrineOrm/Association/Path/Builder/DivergingAssociationPathBuilderTest.php b/tests/Functional/DoctrineOrm/Association/Path/Builder/DivergingAssociationPathBuilderTest.php
new file mode 100644
index 0000000..e7535f3
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Association/Path/Builder/DivergingAssociationPathBuilderTest.php
@@ -0,0 +1,202 @@
+associate('bar')
+ ->aliasAs('bars')
+ ->loadFull()
+ ->associate('baz')
+ ->associate('qux')
+ ->aliasAs('qux')
+ ->loadProxy()
+ ->create();
+
+ $this->assertInstanceOf(DivergingAssociationPath::class, $divergingAssociationPath);
+ $this->assertSame(
+ [
+ '.bar.baz.qux',
+ ],
+ $this->convertDivergingAssociationPathToStrings($divergingAssociationPath)
+ );
+ }
+
+ public function testCase2()
+ {
+ $divergingAssociationPathBuilder = new DivergingAssociationPathBuilder();
+
+ $divergingAssociationPath = $divergingAssociationPathBuilder
+ ->associate('bar')
+ ->associate('baz')
+ ->associate('qux')
+ ->loadFull()
+ ->create();
+
+ $this->assertInstanceOf(DivergingAssociationPath::class, $divergingAssociationPath);
+ $this->assertSame(
+ [
+ '.bar.baz.qux',
+ ],
+ $this->convertDivergingAssociationPathToStrings($divergingAssociationPath)
+ );
+ }
+
+ public function testCase3()
+ {
+ $divergingAssociationPathBuilder = new DivergingAssociationPathBuilder();
+
+ $divergingAssociationPath = $divergingAssociationPathBuilder
+ ->diverge()
+ ->associate('foo')
+ ->diverge()
+ ->associate('bar')
+ ->endDiverge()
+ ->diverge()
+ ->associate('baz')
+ ->aliasAs('.foo.baz')
+ ->loadProxy()
+ ->associate('qux')
+ ->endDiverge()
+ ->endDiverge()
+ ->diverge()
+ ->associate('qux')
+ ->endDiverge()
+ ->create();
+
+ $this->assertInstanceOf(DivergingAssociationPath::class, $divergingAssociationPath);
+ $this->assertSame(
+ [
+ '.foo.bar',
+ '.foo.baz.qux',
+ '.qux',
+ ],
+ $this->convertDivergingAssociationPathToStrings($divergingAssociationPath)
+ );
+ }
+
+ public function testCase4()
+ {
+ $divergingAssociationPathBuilder = new DivergingAssociationPathBuilder();
+
+ $divergingAssociationPath = $divergingAssociationPathBuilder
+ ->associate('foo')
+ ->associate('bar')
+ ->diverge()
+ ->associate('baz')
+ ->associate('qux')
+ ->endDiverge()
+ ->diverge()
+ ->associate('qux')
+ ->endDiverge()
+ ->create();
+
+ $this->assertInstanceOf(DivergingAssociationPath::class, $divergingAssociationPath);
+ $this->assertSame(
+ [
+ '.foo.bar.baz.qux',
+ '.foo.bar.qux',
+ ],
+ $this->convertDivergingAssociationPathToStrings($divergingAssociationPath)
+ );
+ }
+
+ public function testCase5()
+ {
+ $divergingAssociationPathBuilder = new DivergingAssociationPathBuilder();
+
+ $divergingAssociationPath = $divergingAssociationPathBuilder
+ ->diverge()
+ ->associate('foo')
+ ->endDiverge()
+ ->diverge()
+ ->associate('bar')
+ ->endDiverge()
+ ->diverge()
+ ->associate('baz')
+ ->associate('qux')
+ ->endDiverge()
+ ->diverge()
+ ->associate('qux')
+ ->endDiverge()
+ ->create();
+
+ $this->assertInstanceOf(DivergingAssociationPath::class, $divergingAssociationPath);
+ $this->assertSame(
+ [
+ '.foo',
+ '.bar',
+ '.baz.qux',
+ '.qux',
+ ],
+ $this->convertDivergingAssociationPathToStrings($divergingAssociationPath)
+ );
+ }
+
+ public function testCase6()
+ {
+ $divergingAssociationPathBuilder = new DivergingAssociationPathBuilder();
+
+ $divergingAssociationPath = $divergingAssociationPathBuilder
+ ->diverge()
+ ->associate('foo')
+ ->diverge()
+ ->associate('bar')
+ ->diverge()
+ ->associate('baz')
+ ->associate('qux')
+ ->diverge()
+ ->associate('qux')
+ ->create();
+
+ $this->assertInstanceOf(DivergingAssociationPath::class, $divergingAssociationPath);
+ $this->assertSame(
+ [
+ '.foo.bar.baz.qux.qux',
+ ],
+ $this->convertDivergingAssociationPathToStrings($divergingAssociationPath)
+ );
+ }
+
+ /**
+ * @param DivergingAssociationPath $divergingAssociationPath
+ *
+ * @return string[]
+ */
+ protected function convertDivergingAssociationPathToStrings(DivergingAssociationPath $divergingAssociationPath): array
+ {
+ $path = '';
+ foreach ($divergingAssociationPath->getAssociations() as $association) {
+ $path .= sprintf('.%s', $association->getRelationshipName());
+ }
+
+ if (!$divergingAssociationPath->hasChildDivergingAssociationPath()) {
+ return [$path];
+ }
+
+ $childPaths = [];
+ foreach ($divergingAssociationPath->getChildDivergingAssociationPaths() as $childDivergingAssociationPath) {
+ $childPaths = array_merge(
+ $childPaths,
+ $this->convertDivergingAssociationPathToStrings($childDivergingAssociationPath)
+ );
+ }
+ foreach (array_keys($childPaths) as $childPathIndex) {
+ $childPaths[$childPathIndex] = $path . $childPaths[$childPathIndex];
+ }
+
+ return $childPaths;
+ }
+}
diff --git a/tests/Functional/DoctrineOrm/Mock/DataProvider.php b/tests/Functional/DoctrineOrm/Mock/DataProvider.php
new file mode 100644
index 0000000..17ade8e
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Mock/DataProvider.php
@@ -0,0 +1,179 @@
+setName($categoryData['name']);
+ $this->categoriesMap[$category->getId()] = $category;
+ }
+
+ foreach ($data['users'] as $userData) {
+ $user = new User($userData['id']);
+ foreach ($userData['stores'] as $storeData) {
+ $store = new Store($storeData['id']);
+ $store->setUser($user);
+ $user->addStore($store);
+ $store->setApproved($storeData['approved']);
+ foreach ($storeData['products'] as $productData) {
+ $product = new Product($productData['id']);
+ $product->setStore($store);
+ $store->addProduct($product);
+ $product->setApproved($productData['approved']);
+ $product->setName($productData['name']);
+ $product->setCategory($this->categoriesMap[$productData['categoryId']]);
+ foreach ($productData['variants'] as $variantData) {
+ $variant = new Variant($variantData['id']);
+ $variant->setProduct($product);
+ $product->addVariant($variant);
+ $variant->setPrice($variantData['price']);
+ $this->variantsMap[$variant->getId()] = $variant;
+ }
+ $this->productsMap[$product->getId()] = $product;
+ }
+ $this->storesMap[$store->getId()] = $store;
+ }
+ $this->usersMap[$user->getId()] = $user;
+ }
+ }
+
+ /**
+ * @return User[]
+ */
+ public function getUsers(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->usersMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->usersMap);
+ }
+
+ /**
+ * @return Store[]
+ */
+ public function getStores(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->storesMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->storesMap);
+ }
+
+ /**
+ * @return Product[]
+ */
+ public function getProducts(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->productsMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->productsMap);
+ }
+
+ /**
+ * @return Variant[]
+ */
+ public function getVariants(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->variantsMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->variantsMap);
+ }
+
+ /**
+ * @return Category[]
+ */
+ public function getCategories(array $ids = null): array
+ {
+ if (is_array($ids)) {
+ return array_values(
+ array_intersect_key(
+ $this->categoriesMap,
+ array_flip($ids)
+ )
+ );
+ }
+
+ return array_values($this->categoriesMap);
+ }
+
+ /**
+ * @return array
+ */
+ public function getAll(): array
+ {
+ return array_merge(
+ $this->getCategories(),
+ $this->getUsers(),
+ $this->getStores(),
+ $this->getProducts(),
+ $this->getVariants()
+ );
+ }
+}
diff --git a/tests/Functional/DoctrineOrm/Mock/DataProviderInterface.php b/tests/Functional/DoctrineOrm/Mock/DataProviderInterface.php
new file mode 100644
index 0000000..2feaea4
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Mock/DataProviderInterface.php
@@ -0,0 +1,36 @@
+ 'pdo_sqlite',
+ 'dbname' => 'sifter_test', // TODO Move to some config file.
+ 'user' => 'root', // TODO Move to some config file.
+ 'password' => '', // TODO Move to some config file.
+ 'memory' => true, // TODO Move to some config file.
+ ];
+
+ $metadataConfiguration = Setup::createAnnotationMetadataConfiguration($entityPaths, $isDevMode);
+ $this->entityManager = EntityManager::create($databaseOptions, $metadataConfiguration);
+ }
+
+ /**
+ * @return EntityManagerInterface
+ */
+ public function getEntityManager(): EntityManagerInterface
+ {
+ if (!$this->entityManager instanceof EntityManagerInterface) {
+ $this->setupEntityManager();
+ }
+
+ return $this->entityManager;
+ }
+
+ /**
+ * @throws \Doctrine\ORM\Tools\ToolsException
+ */
+ public function createSchema()
+ {
+ $this->getEntityManager();
+
+ $entityClassMetadatas = [];
+ foreach ($this->getEntityClassNames() as $entityClassName) {
+ $entityClassMetadatas[] = $this->entityManager->getClassMetadata($entityClassName);
+ }
+
+ $tool = new SchemaTool($this->entityManager);
+ $tool->createSchema($entityClassMetadatas);
+ }
+
+ public function loadData()
+ {
+ $this->getEntityManager();
+
+ $dataProvider = new DataProvider();
+ foreach ($dataProvider->getAll() as $entity) {
+ $this->entityManager->persist($entity);
+ }
+ $this->entityManager->flush();
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getEntityClassNames(): array
+ {
+ return [
+ Category::class,
+ User::class,
+ Store::class,
+ Product::class,
+ Variant::class,
+ ];
+ }
+}
diff --git a/tests/Functional/DoctrineOrm/Mock/Entity/Category.php b/tests/Functional/DoctrineOrm/Mock/Entity/Category.php
new file mode 100644
index 0000000..47a8467
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Mock/Entity/Category.php
@@ -0,0 +1,93 @@
+id = $id;
+
+ $this->products = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param Product[] $products
+ */
+ public function setProducts(array $products)
+ {
+ $this->products->clear();
+ foreach ($products as $product) {
+ $this->products->add($product);
+ }
+ }
+
+ /**
+ * @return Collection|Product[]
+ */
+ public function getProducts(): Collection
+ {
+ return $this->products;
+ }
+}
diff --git a/tests/Functional/DoctrineOrm/Mock/Entity/Product.php b/tests/Functional/DoctrineOrm/Mock/Entity/Product.php
new file mode 100644
index 0000000..7e1444f
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Mock/Entity/Product.php
@@ -0,0 +1,171 @@
+id = $id;
+
+ $this->variants = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param string $name
+ */
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param bool $approved
+ */
+ public function setApproved(bool $approved)
+ {
+ $this->approved = $approved;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isApproved(): bool
+ {
+ return $this->approved;
+ }
+
+ /**
+ * @param Store $store
+ */
+ public function setStore(Store $store)
+ {
+ $this->store = $store;
+ }
+
+ /**
+ * @return Store
+ */
+ public function getStore(): Store
+ {
+ return $this->store;
+ }
+
+ /**
+ * @param Category $category
+ */
+ public function setCategory(Category $category)
+ {
+ $this->category = $category;
+ }
+
+ /**
+ * @return Category
+ */
+ public function getCategory(): Category
+ {
+ return $this->category;
+ }
+
+ /**
+ * @param Variant[] $variants
+ */
+ public function setVariants(array $variants)
+ {
+ $this->variants->clear();
+ foreach ($variants as $variant) {
+ $this->variants->add($variant);
+ }
+ }
+
+ /**
+ * @param Variant $variant
+ */
+ public function addVariant(Variant $variant)
+ {
+ $this->variants->add($variant);
+ }
+
+ /**
+ * @return Collection|Variant[]
+ */
+ public function getVariants()
+ {
+ return $this->variants;
+ }
+}
diff --git a/tests/Functional/DoctrineOrm/Mock/Entity/Store.php b/tests/Functional/DoctrineOrm/Mock/Entity/Store.php
new file mode 100644
index 0000000..a4032eb
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Mock/Entity/Store.php
@@ -0,0 +1,148 @@
+id = $id;
+
+ $this->products = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+// /**
+// * @param string $name
+// */
+// public function setName(string $name)
+// {
+// $this->name = $name;
+// }
+//
+// /**
+// * @return string
+// */
+// public function getName(): string
+// {
+// return $this->name;
+// }
+
+ /**
+ * @param bool $approved
+ */
+ public function setApproved(bool $approved)
+ {
+ $this->approved = $approved;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isApproved(): bool
+ {
+ return $this->approved;
+ }
+
+ /**
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ }
+
+ /**
+ * @return User
+ */
+ public function getUser(): User
+ {
+ return $this->user;
+ }
+
+ /**
+ * @param Product[] $products
+ */
+ public function setProducts(array $products)
+ {
+ $this->products->clear();
+ foreach ($products as $product) {
+ $this->products->add($product);
+ }
+ }
+
+ /**
+ * @param Product $product
+ */
+ public function addProduct(Product $product)
+ {
+ $this->products->add($product);
+ }
+
+ /**
+ * @return Collection|Product[]
+ */
+ public function getProducts(): Collection
+ {
+ return $this->products;
+ }
+}
diff --git a/tests/Functional/DoctrineOrm/Mock/Entity/User.php b/tests/Functional/DoctrineOrm/Mock/Entity/User.php
new file mode 100644
index 0000000..73671c9
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Mock/Entity/User.php
@@ -0,0 +1,78 @@
+id = $id;
+
+ $this->stores = new ArrayCollection();
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param Store[] $stores
+ */
+ public function setStores(array $stores)
+ {
+ $this->stores->clear();
+ foreach ($stores as $store) {
+ $this->stores->add($store);
+ }
+ }
+
+ /**
+ * @param Store $store
+ */
+ public function addStore(Store $store)
+ {
+ $this->stores->add($store);
+ }
+
+ /**
+ * @return Collection|Store[]
+ */
+ public function getStores(): Collection
+ {
+ return $this->stores;
+ }
+}
diff --git a/tests/Functional/DoctrineOrm/Mock/Entity/Variant.php b/tests/Functional/DoctrineOrm/Mock/Entity/Variant.php
new file mode 100644
index 0000000..3ffcfd8
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Mock/Entity/Variant.php
@@ -0,0 +1,86 @@
+id = $id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param int $price
+ */
+ public function setPrice(int $price)
+ {
+ $this->price = $price;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPrice(): int
+ {
+ return $this->price;
+ }
+
+ /**
+ * @param Product $product
+ */
+ public function setProduct(Product $product)
+ {
+ $this->product = $product;
+ }
+
+ /**
+ * @return Product
+ */
+ public function getProduct(): Product
+ {
+ return $this->product;
+ }
+}
diff --git a/tests/Functional/DoctrineOrm/Mock/data.yml b/tests/Functional/DoctrineOrm/Mock/data.yml
new file mode 100644
index 0000000..077ce58
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Mock/data.yml
@@ -0,0 +1,220 @@
+categories:
+ -
+ id: c1
+ name: 'Category 1'
+ -
+ id: c2
+ name: 'Category 2'
+ -
+ id: c3
+ name: 'Category 3'
+
+users:
+ -
+ id: u1
+ stores:
+ -
+ id: s1
+ approved: false
+ products:
+ -
+ id: s1p1
+ approved: false
+ name: Lorem
+ categoryId: c1
+ variants:
+ -
+ id: s1p1v1
+ price: 999
+ -
+ id: s1p1v2
+ price: 120
+ -
+ id: s1p1v3
+ price: 121
+ -
+ id: s1p1v4
+ price: 160
+ -
+ id: s1p2
+ approved: true
+ name: Ipsum
+ categoryId: c2
+ variants:
+ -
+ id: s1p2v1
+ price: 100
+ -
+ id: s1p2v2
+ price: 220
+ -
+ id: s1p2v3
+ price: 300
+ -
+ id: s1p2v4
+ price: 500
+ -
+ id: s1p3
+ approved: true
+ name: Ipsum
+ categoryId: c1
+ variants:
+ -
+ id: s1p3v1
+ price: 10
+ -
+ id: s1p3v2
+ price: 500
+ -
+ id: s2
+ approved: false
+ products:
+ -
+ id: s2p1
+ approved: true
+ name: Sit
+ categoryId: c3
+ variants:
+ -
+ id: s2p1v1
+ price: 600
+ -
+ id: s2p1v2
+ price: 520
+ -
+ id: s2p1v3
+ price: 321
+ -
+ id: s2p1v4
+ price: 460
+ -
+ id: s2p2
+ approved: false
+ name: Amet
+ categoryId: c2
+ variants:
+ -
+ id: s2p2v1
+ price: 500
+ -
+ id: s2p2v2
+ price: 300
+ -
+ id: s2p2v3
+ price: 200
+ -
+ id: s2p2v4
+ price: 100
+ -
+ id: s2p3
+ approved: true
+ name: Nullam
+ categoryId: c1
+ variants:
+ -
+ id: s2p3v1
+ price: 10
+ -
+ id: u2
+ stores:
+ -
+ id: s3
+ approved: true
+ products:
+ -
+ id: s3p1
+ approved: false
+ name: Ipsum
+ categoryId: c2
+ variants:
+ -
+ id: s3p1v1
+ price: 100
+ -
+ id: s3p1v2
+ price: 120
+ -
+ id: s3p1v3
+ price: 121
+ -
+ id: s3p1v4
+ price: 160
+ -
+ id: s3p2
+ approved: true
+ name: Lorem
+ categoryId: c2
+ variants:
+ -
+ id: s3p2v1
+ price: 100
+ -
+ id: s3p2v2
+ price: 220
+ -
+ id: s3p2v3
+ price: 300
+ -
+ id: s3p2v4
+ price: 500
+ -
+ id: s3p3
+ approved: true
+ name: Qui
+ categoryId: c1
+ variants:
+ -
+ id: s3p3v1
+ price: 10
+ -
+ id: s3p3v2
+ price: 500
+ -
+ id: s4
+ approved: true
+ products:
+ -
+ id: s4p1
+ approved: true
+ name: Vestibulum
+ categoryId: c3
+ variants:
+ -
+ id: s4p1v1
+ price: 600
+ -
+ id: s4p1v2
+ price: 520
+ -
+ id: s4p1v3
+ price: 321
+ -
+ id: s4p1v4
+ price: 460
+ -
+ id: s4p2
+ approved: true
+ name: Amet
+ categoryId: c2
+ variants:
+ -
+ id: s4p2v1
+ price: 500
+ -
+ id: s4p2v2
+ price: 300
+ -
+ id: s4p2v3
+ price: 200
+ -
+ id: s4p2v4
+ price: 100
+ -
+ id: s4p3
+ approved: false
+ name: Nullam
+ categoryId: c3
+ variants:
+ -
+ id: s4p3v1
+ price: 10
diff --git a/tests/Functional/DoctrineOrm/Source/EntityClassHelperTest.php b/tests/Functional/DoctrineOrm/Source/EntityClassHelperTest.php
new file mode 100644
index 0000000..0dbf569
--- /dev/null
+++ b/tests/Functional/DoctrineOrm/Source/EntityClassHelperTest.php
@@ -0,0 +1,131 @@
+entityManager = $doctrineOrmHelper->getEntityManager();
+
+ $metadataAdapterProvider = new MetadataAdapterProvider($this->entityManager);
+ $entityClassHelper = new EntityClassHelper($metadataAdapterProvider);
+
+ $entitySource = new EntitySource($entities);
+ $entityClassHelper->supplementEntitySource($entitySource);
+
+ $this->assertEquals($expectedClassName, $entitySource->getEntityClass());
+
+ $this->assertContains($expectedClassName, [
+ $entitySource->getClassMetadataAdapter()->getClassName(),
+ $entitySource->getClassMetadataAdapter()->getRootClassName(),
+ ]);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataSetEntityClass(): array
+ {
+ $this->dataProvider = new DataProvider();
+
+ return [
+ [
+ $this->dataProvider->getProducts(['s1p1', 's1p3', 's3p1']),
+ Product::class,
+ ],
+ [
+ $this->dataProvider->getStores(['s1', 's2']),
+ Store::class,
+ ],
+ [
+ $this->dataProvider->getVariants(['s3p1v1']),
+ Variant::class,
+ ],
+ ];
+ }
+
+ /**
+ * @param object[] $entities
+ *
+ * @dataProvider dataSetEntityClassIfNoCommonClassName
+ * @expectedException \Exception
+ */
+ public function testSetEntityClassIfNoCommonClassName(array $entities): void
+ {
+ $doctrineOrmHelper = new DoctrineOrmHelper();
+ $this->entityManager = $doctrineOrmHelper->getEntityManager();
+
+ $metadataAdapterProvider = new MetadataAdapterProvider($this->entityManager);
+ $entityClassHelper = new EntityClassHelper($metadataAdapterProvider);
+
+ $entitySource = new EntitySource($entities);
+ $entityClassHelper->supplementEntitySource($entitySource);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataSetEntityClassIfNoCommonClassName(): array
+ {
+ $this->dataProvider = new DataProvider();
+
+ return [
+ [
+ [
+ $this->dataProvider->getProducts(['s1p1'])[0],
+ $this->dataProvider->getStores(['s2'])[0],
+ $this->dataProvider->getProducts(['s3p1'])[0],
+ ],
+ ],
+ [
+ [
+ $this->dataProvider->getStores(['s1'])[0],
+ $this->dataProvider->getStores(['s2'])[0],
+ $this->dataProvider->getProducts(['s1p3'])[0],
+ ],
+ ],
+ [
+ [
+ $this->dataProvider->getVariants(['s3p1v1'])[0],
+ $this->dataProvider->getStores(['s1'])[0],
+ $this->dataProvider->getProducts(['s2p1'])[0],
+ ],
+ ],
+ [
+ [
+ new \stdClass(),
+ new \stdClass(),
+ ],
+ ],
+ ];
+ }
+}