├── .coveralls.yml
├── .gitignore
├── tests
├── ORMTestInfrastructure
│ ├── Fixtures
│ │ ├── Importer
│ │ │ ├── ReturnEntities.php
│ │ │ └── LoadEntities.php
│ │ ├── EntityNamespace2
│ │ │ ├── TestEntity.php
│ │ │ └── TestEntityWithDependency.php
│ │ └── EntityNamespace1
│ │ │ ├── InterfaceAssociation
│ │ │ ├── EntityInterface.php
│ │ │ ├── EntityImplementation.php
│ │ │ └── EntityWithAssociationAgainstInterface.php
│ │ │ ├── DependencyResolverFixtures
│ │ │ ├── SingleEntity
│ │ │ │ └── Entity.php
│ │ │ ├── TransientBaseClass
│ │ │ │ └── Entity.php
│ │ │ ├── MappedSuperclassInheritance
│ │ │ │ └── Entity.php
│ │ │ ├── JoinedTableInheritance
│ │ │ │ └── Entity.php
│ │ │ ├── SingleTableInheritance
│ │ │ │ └── Entity.php
│ │ │ ├── TwoEntitiesInheritance
│ │ │ │ └── Entity.php
│ │ │ ├── JoinedTableInheritanceWithTwoSubclasses
│ │ │ │ └── Entity.php
│ │ │ └── JoinedTableInheritanceWithTwoLevels
│ │ │ │ └── Entity.php
│ │ │ ├── TestEntityRepository.php
│ │ │ ├── Inheritance
│ │ │ ├── DiscriminatorMapChildEntity.php
│ │ │ ├── MappedSuperClassChild.php
│ │ │ ├── ClassTableChildEntity.php
│ │ │ ├── ClassTableChildWithParentReferenceEntity.php
│ │ │ ├── ClassTableParentEntity.php
│ │ │ ├── DiscriminatorMapEntity.php
│ │ │ ├── MappedSuperClassParentWithReference.php
│ │ │ └── ClassTableParentWithReferenceEntity.php
│ │ │ ├── ReferencedEntity.php
│ │ │ ├── Cascade
│ │ │ ├── CascadePersistedEntity.php
│ │ │ └── CascadePersistingEntity.php
│ │ │ ├── TestEntity.php
│ │ │ ├── TestEntityWithDependency.php
│ │ │ ├── ReferenceCycleEntity.php
│ │ │ └── ChainReferenceEntity.php
│ ├── ConfigurationFactoryTest.php
│ ├── QueryTest.php
│ ├── EntityListDriverDecoratorTest.php
│ ├── ImporterTest.php
│ ├── EntityDependencyResolverTest.php
│ └── ORMInfrastructureTest.php
└── Config
│ ├── InMemoryDatabaseConnectionConfigurationTest.php
│ ├── ExistingConnectionConfigurationTest.php
│ ├── ConnectionConfigurationTest.php
│ └── FileDatabaseConnectionConfigurationTest.php
├── src
├── Config
│ ├── InMemoryDatabaseConnectionConfiguration.php
│ ├── ExistingConnectionConfiguration.php
│ ├── ConnectionConfiguration.php
│ └── FileDatabaseConnectionConfiguration.php
└── ORMTestInfrastructure
│ ├── QueryLogger.php
│ ├── Query.php
│ ├── EntityListDriverDecorator.php
│ ├── ConfigurationFactory.php
│ ├── Importer.php
│ ├── EntityDependencyResolver.php
│ └── ORMInfrastructure.php
├── phpunit.xml.dist
├── LICENSE
├── .github
└── workflows
│ ├── dependencies.yml
│ └── tests.yml
├── composer.json
├── CHANGELOG.md
└── README.md
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 | coverage_clover: build/logs/clover.xml
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | phpunit.xml$
3 | .phpunit.result.cache
4 | composer.lock
5 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/Importer/ReturnEntities.php:
--------------------------------------------------------------------------------
1 | persist(new \stdClass());
5 | $objectManager->persist(new \stdClass());
6 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace2/TestEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1;
11 |
12 | use Doctrine\ORM\EntityRepository;
13 |
14 | /**
15 | * A custom repository for test entities.
16 | */
17 | class TestEntityRepository extends EntityRepository
18 | {
19 | }
20 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/DependencyResolverFixtures/TransientBaseClass/Entity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | #[ORM\Entity]
15 | class DiscriminatorMapChildEntity extends DiscriminatorMapEntity
16 | {
17 | /**
18 | * @var string
19 | */
20 | #[ORM\Column(type: 'string', name: 'child_name', nullable: false)]
21 | public $childName = 'child-name';
22 | }
23 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/Inheritance/MappedSuperClassChild.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | #[ORM\Entity]
15 | class MappedSuperClassChild extends MappedSuperClassParentWithReference
16 | {
17 | /**
18 | * @var string
19 | */
20 | #[ORM\Column(type: 'string', name: 'child_name', nullable: false)]
21 | public $childName = 'child-name';
22 | }
23 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/DependencyResolverFixtures/JoinedTableInheritance/Entity.php:
--------------------------------------------------------------------------------
1 | 'BaseEntity', 'sub' => 'Entity'])]
11 | class BaseEntity
12 | {
13 | #[ORM\Column(type: 'integer')]
14 | #[ORM\Id]
15 | private $id;
16 |
17 | #[ORM\Column]
18 | protected $fieldA;
19 | }
20 |
21 | #[ORM\Entity]
22 | class Entity extends BaseEntity
23 | {
24 | #[ORM\Column]
25 | protected $fieldB;
26 | }
27 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/DependencyResolverFixtures/SingleTableInheritance/Entity.php:
--------------------------------------------------------------------------------
1 | 'BaseEntity', 'sub' => 'Entity'])]
11 | class BaseEntity
12 | {
13 | #[ORM\Column(type: 'integer')]
14 | #[ORM\Id]
15 | private $id;
16 |
17 | #[ORM\Column]
18 | protected $fieldA;
19 | }
20 |
21 | #[ORM\Entity]
22 | class Entity extends BaseEntity
23 | {
24 | #[ORM\Column]
25 | protected $fieldB;
26 | }
27 |
--------------------------------------------------------------------------------
/src/Config/InMemoryDatabaseConnectionConfiguration.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Config;
11 |
12 | /**
13 | * Specifies a connection to an in-memory SQLite database.
14 | */
15 | class InMemoryDatabaseConnectionConfiguration extends ConnectionConfiguration
16 | {
17 | /**
18 | * Creates a connection configuration that connects to an in-memory database.
19 | */
20 | public function __construct()
21 | {
22 | parent::__construct([
23 | 'driver' => 'pdo_sqlite',
24 | 'memory' => true
25 | ]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace2/TestEntityWithDependency.php:
--------------------------------------------------------------------------------
1 | dependency = new ReferencedEntity();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/ReferencedEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * An entity that is referenced by another one.
16 | */
17 | #[ORM\Table(name: 'referenced_entity')]
18 | #[ORM\Entity]
19 | class ReferencedEntity
20 | {
21 | /**
22 | * A unique ID.
23 | *
24 | * @var integer|null
25 | */
26 | #[ORM\Id]
27 | #[ORM\Column(type: 'integer', name: 'id')]
28 | #[ORM\GeneratedValue]
29 | public $id = null;
30 | }
31 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/DependencyResolverFixtures/TwoEntitiesInheritance/Entity.php:
--------------------------------------------------------------------------------
1 | Superclass::class, 'entity' => Entity::class])]
12 | class Superclass
13 | {
14 | #[ORM\Column(type: 'integer')]
15 | #[ORM\Id]
16 | private $id;
17 |
18 | #[ORM\Column]
19 | protected $fieldA;
20 | }
21 |
22 | #[ORM\Entity]
23 | class Entity extends Superclass
24 | {
25 | #[ORM\Column]
26 | protected $fieldB;
27 | }
28 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/InterfaceAssociation/EntityImplementation.php:
--------------------------------------------------------------------------------
1 | id;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/InterfaceAssociation/EntityWithAssociationAgainstInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Child class that uses class table inheritance.
16 | *
17 | * @see http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#class-table-inheritance
18 | */
19 | #[ORM\Table(name: 'class_inheritance_child')]
20 | #[ORM\Entity]
21 | class ClassTableChildEntity extends ClassTableParentEntity
22 | {
23 | /**
24 | * @var string
25 | */
26 | #[ORM\Column(type: 'string', name: 'child_name', nullable: false)]
27 | public $childName = 'child-name';
28 | }
29 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/DependencyResolverFixtures/JoinedTableInheritanceWithTwoSubclasses/Entity.php:
--------------------------------------------------------------------------------
1 | 'BaseEntity', 'first' => 'Entity', 'second' => 'SecondEntity'])]
11 | class BaseEntity
12 | {
13 | #[ORM\Column(type: 'integer')]
14 | #[ORM\Id]
15 | private $id;
16 |
17 | #[ORM\Column]
18 | protected $fieldA;
19 | }
20 |
21 | #[ORM\Entity]
22 | class SecondEntity extends BaseEntity
23 | {
24 | #[ORM\Column]
25 | protected $fieldB;
26 | }
27 |
28 | #[ORM\Entity]
29 | class Entity extends BaseEntity
30 | {
31 | #[ORM\Column]
32 | protected $fieldC;
33 | }
34 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | src
12 |
13 |
14 |
15 |
16 | tests/
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/DependencyResolverFixtures/JoinedTableInheritanceWithTwoLevels/Entity.php:
--------------------------------------------------------------------------------
1 | 'BaseEntity', 'intermediate' => 'IntermediateEntity', 'child' => 'Entity'])]
11 | class BaseEntity
12 | {
13 | #[ORM\Column(type: 'integer')]
14 | #[ORM\Id]
15 | private $id;
16 |
17 | #[ORM\Column]
18 | protected $fieldA;
19 | }
20 |
21 | #[ORM\Entity]
22 | class IntermediateEntity extends BaseEntity
23 | {
24 | #[ORM\Column]
25 | protected $fieldB;
26 | }
27 |
28 | #[ORM\Entity]
29 | class Entity extends IntermediateEntity
30 | {
31 | #[ORM\Column]
32 | protected $fieldC;
33 | }
34 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/Cascade/CascadePersistedEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Cascade;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Entity that automatically persists its associated entities.
16 | */
17 | #[ORM\Table(name: 'cascade_persisted_entity')]
18 | #[ORM\Entity]
19 | class CascadePersistedEntity
20 | {
21 | /**
22 | * A unique ID.
23 | *
24 | * @var integer|null
25 | */
26 | #[ORM\Id]
27 | #[ORM\Column(type: 'integer', name: 'id')]
28 | #[ORM\GeneratedValue]
29 | public $id = null;
30 |
31 | /**
32 | * @var CascadePersistingEntity
33 | */
34 | #[ORM\ManyToOne(targetEntity: \CascadePersistingEntity::class)]
35 | public $parent;
36 | }
37 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/TestEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Doctrine entity that is used for testing.
16 | */
17 | #[ORM\Table(name: 'test_entity')]
18 | #[ORM\Entity(repositoryClass: \TestEntityRepository::class)]
19 | class TestEntity
20 | {
21 | /**
22 | * A unique ID.
23 | *
24 | * @var integer|null
25 | */
26 | #[ORM\Id]
27 | #[ORM\Column(type: 'integer', name: 'id')]
28 | #[ORM\GeneratedValue]
29 | public $id = null;
30 |
31 | /**
32 | * A string property.
33 | *
34 | * @var string
35 | */
36 | #[ORM\Column(type: 'string', name: 'name', nullable: true)]
37 | public $name = null;
38 | }
39 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/Inheritance/ClassTableChildWithParentReferenceEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Child class that uses class table inheritance with a parent that references another entity.
16 | *
17 | * @see http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#class-table-inheritance
18 | */
19 | #[ORM\Table(name: 'class_inheritance_with_reference_child')]
20 | #[ORM\Entity]
21 | class ClassTableChildWithParentReferenceEntity extends ClassTableParentWithReferenceEntity
22 | {
23 | /**
24 | * @var string
25 | */
26 | #[ORM\Column(type: 'string', name: 'child_name', nullable: false)]
27 | public $childName = 'child-name';
28 | }
29 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/Inheritance/ClassTableParentEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Base class for entities with class table strategy.
16 | *
17 | * @see http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#class-table-inheritance
18 | */
19 | #[ORM\Table(name: 'class_inheritance_parent')]
20 | #[ORM\Entity]
21 | #[ORM\InheritanceType('JOINED')]
22 | #[ORM\DiscriminatorColumn(name: 'class', type: 'string')]
23 | abstract class ClassTableParentEntity
24 | {
25 | /**
26 | * A unique ID.
27 | *
28 | * @var integer|null
29 | */
30 | #[ORM\Id]
31 | #[ORM\Column(type: 'integer', name: 'id')]
32 | #[ORM\GeneratedValue]
33 | public $id = null;
34 | }
35 |
--------------------------------------------------------------------------------
/src/ORMTestInfrastructure/QueryLogger.php:
--------------------------------------------------------------------------------
1 | enabled) {
16 | return;
17 | }
18 |
19 | if (str_starts_with($message, 'Executing')) {
20 | $this->queries[] = new Query($context['sql'], $context['params'] ?? []);
21 | } else if ('Beginning transaction' === $message) {
22 | $this->queries[] = new Query('"START TRANSACTION"', []);
23 | } else if ('Committing transaction' === $message) {
24 | $this->queries[] = new Query('"COMMIT"', []);
25 | } else if ('Rolling back transaction' === $message) {
26 | $this->queries[] = new Query('"ROLLBACK"', []);
27 | }
28 | }
29 |
30 | public function getQueries()
31 | {
32 | return $this->queries;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Config/InMemoryDatabaseConnectionConfigurationTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\Config;
11 |
12 | use PHPUnit\Framework\TestCase;
13 | use Webfactory\Doctrine\Config\InMemoryDatabaseConnectionConfiguration;
14 | use Webfactory\Doctrine\ORMTestInfrastructure\ORMInfrastructure;
15 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\TestEntity;
16 |
17 | class InMemoryDatabaseConnectionConfigurationTest extends TestCase
18 | {
19 | /**
20 | * Checks if the connection configuration *really* works with the infrastructure.
21 | */
22 | public function testWorksWithInfrastructure()
23 | {
24 | $configuration = new InMemoryDatabaseConnectionConfiguration();
25 |
26 | $infrastructure = ORMInfrastructure::createOnlyFor(TestEntity::class, $configuration);
27 | $this->assertNull(
28 | $infrastructure->import(new TestEntity())
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 webfactory GmbH, Bonn (info@webfactory.de)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to
7 | deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/src/Config/ExistingConnectionConfiguration.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
25 | }
26 |
27 | /**
28 | * Provides the existing DBAL connection
29 | *
30 | * This makes use of the fact that the first argument to EntityManager::create() is in fact
31 | * un-typed: You can pass in either a configuration array or an existing DBAL connection.
32 | *
33 | * @return Connection
34 | */
35 | public function getConnectionParameters()
36 | {
37 | return $this->connection;
38 | }
39 |
40 | public function getConnection(): Connection
41 | {
42 | return $this->connection;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/Inheritance/DiscriminatorMapEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Base entity with an explicit discriminator map.
16 | *
17 | * The discriminator map contains fully qualified as well as relative entity class names.
18 | *
19 | * @see http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#class-table-inheritance
20 | */
21 | #[ORM\Entity]
22 | #[ORM\InheritanceType('JOINED')]
23 | #[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
24 | #[ORM\DiscriminatorMap(['parent' => 'Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance\DiscriminatorMapEntity', 'child' => 'DiscriminatorMapChildEntity'])]
25 | class DiscriminatorMapEntity
26 | {
27 | /**
28 | * A unique ID.
29 | *
30 | * @var integer|null
31 | */
32 | #[ORM\Id]
33 | #[ORM\Column(type: 'integer', name: 'id')]
34 | #[ORM\GeneratedValue]
35 | public $id = null;
36 | }
37 |
--------------------------------------------------------------------------------
/.github/workflows/dependencies.yml:
--------------------------------------------------------------------------------
1 | name: AllDependenciesDeclared
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | env:
10 | PHP_VERSION: 8.4
11 |
12 | jobs:
13 | composer-require-checker:
14 | name: Check missing composer requirements
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Configure PHP version
18 | uses: shivammathur/setup-php@v2
19 | with:
20 | php-version: ${{ env.PHP_VERSION }}
21 | coverage: none
22 | tools: composer:v2
23 | - uses: actions/checkout@v4
24 | - name: Cache Composer Dependencies
25 | uses: actions/cache@v4
26 | with:
27 | path: vendor/
28 | key: composer-${{ env.PHP_VERSION }}-${{ hashFiles('composer.*') }}
29 | restore-keys: |
30 | composer-${{ env.PHP_VERSION }}-${{ github.ref }}
31 | composer-${{ env.PHP_VERSION }}-
32 | - run: |
33 | composer update --no-interaction --no-scripts --no-progress
34 | composer show
35 | - name: ComposerRequireChecker
36 | uses: docker://ghcr.io/webfactory/composer-require-checker:4.12.0
37 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/TestEntityWithDependency.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Test entity that references another entity and therefore implicitly depends
16 | * on it in test scenarios.
17 | */
18 | #[ORM\Table(name: 'test_entity_with_dependency')]
19 | #[ORM\Entity]
20 | class TestEntityWithDependency
21 | {
22 | /**
23 | * A unique ID.
24 | *
25 | * @var integer|null
26 | */
27 | #[ORM\Id]
28 | #[ORM\Column(type: 'integer', name: 'id')]
29 | #[ORM\GeneratedValue]
30 | public $id = null;
31 |
32 | /**
33 | * Required reference to another entity.
34 | *
35 | * @var ReferencedEntity
36 | */
37 | #[ORM\JoinColumn(nullable: false)]
38 | #[ORM\OneToOne(targetEntity: \ReferencedEntity::class, cascade: ['all'])]
39 | protected $dependency = null;
40 |
41 | /**
42 | * Automatically creates a reference on construction.
43 | */
44 | public function __construct()
45 | {
46 | $this->dependency = new ReferencedEntity();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webfactory/doctrine-orm-test-infrastructure",
3 | "description": "Provides utils to create a test infrastructure for Doctrine 2 entities.",
4 | "keywords": [
5 | "Doctrine",
6 | "ORM",
7 | "testing"
8 | ],
9 | "license": "MIT",
10 | "authors": [
11 | {
12 | "name": "webfactory GmbH",
13 | "email": "info@webfactory.de",
14 | "homepage": "http://www.webfactory.de",
15 | "role": "Developer"
16 | }
17 | ],
18 | "require": {
19 | "php": ">= 8.1",
20 | "doctrine/common": "^3.0",
21 | "doctrine/dbal": "^3.2",
22 | "doctrine/event-manager": "^1.1|^2.0",
23 | "doctrine/orm": "^2.20|^3.0",
24 | "doctrine/persistence": "^2.5|^3.0|^4.0",
25 | "psr/log": "^2.0|^3.0",
26 | "symfony/cache": "^6.4|^7.0"
27 | },
28 | "require-dev": {
29 | "doctrine/collections": "^1.6.8|^2.2.1",
30 | "phpunit/phpunit": "^10.5.58",
31 | "symfony/var-exporter": "^6.4|^7.3"
32 | },
33 | "autoload": {
34 | "psr-4": {
35 | "Webfactory\\Doctrine\\": "src/"
36 | }
37 | },
38 | "autoload-dev": {
39 | "psr-4": {
40 | "Webfactory\\Doctrine\\Tests\\": "tests/"
41 | }
42 | },
43 | "config": {
44 | "sort-packages": true
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/ReferenceCycleEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Entity with a minimal reference cycle.
16 | */
17 | #[ORM\Table(name: 'test_reference_cycle_entity')]
18 | #[ORM\Entity]
19 | class ReferenceCycleEntity
20 | {
21 | /**
22 | * A unique ID.
23 | *
24 | * @var integer|null
25 | */
26 | #[ORM\Id]
27 | #[ORM\Column(type: 'integer', name: 'id')]
28 | #[ORM\GeneratedValue]
29 | public $id = null;
30 |
31 | /**
32 | * Reference to an entity of the same type (minimal cycle).
33 | *
34 | * @var ReferenceCycleEntity|null
35 | */
36 | #[ORM\JoinColumn(nullable: true)]
37 | #[ORM\OneToOne(targetEntity: \ReferenceCycleEntity::class, cascade: ['all'])]
38 | protected $referenceCycle = null;
39 |
40 | /**
41 | * Creates an entity that references the provided entity.
42 | *
43 | * @param ReferenceCycleEntity|null $entity
44 | */
45 | public function __construct(?ReferenceCycleEntity $entity = null)
46 | {
47 | $this->referenceCycle = $entity;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/ConfigurationFactoryTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure;
11 |
12 | use Doctrine\ORM\Configuration;
13 | use PHPUnit\Framework\TestCase;
14 | use Webfactory\Doctrine\ORMTestInfrastructure\ConfigurationFactory;
15 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\TestEntity;
16 |
17 | /**
18 | * Tests the ORM configuration factory.
19 | */
20 | class ConfigurationFactoryTest extends TestCase
21 | {
22 | /**
23 | * System under test.
24 | *
25 | * @var ConfigurationFactory
26 | */
27 | protected $factory = null;
28 |
29 | /**
30 | * Initializes the test environment.
31 | */
32 | protected function setUp(): void
33 | {
34 | parent::setUp();
35 | $this->factory = new ConfigurationFactory();
36 | }
37 |
38 | /**
39 | * Ensures that createFor() returns an ORM configuration object.
40 | */
41 | public function testCreateForReturnsConfiguration()
42 | {
43 | $configuration = $this->factory->createFor(array(
44 | TestEntity::class,
45 | ));
46 |
47 | $this->assertInstanceOf(Configuration::class, $configuration);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/ChainReferenceEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 |
14 | /**
15 | * Entity that references an entity indirectly (over another reference).
16 | *
17 | * Reference chain:
18 | *
19 | * ChainReferenceEntity -> TestEntityWithDependency -> ReferencedEntity
20 | */
21 | #[ORM\Table(name: 'test_chain_reference_entity')]
22 | #[ORM\Entity]
23 | class ChainReferenceEntity
24 | {
25 | /**
26 | * A unique ID.
27 | *
28 | * @var integer|null
29 | */
30 | #[ORM\Id]
31 | #[ORM\Column(type: 'integer', name: 'id')]
32 | #[ORM\GeneratedValue]
33 | public $id = null;
34 |
35 | /**
36 | * Required reference to another entity.
37 | *
38 | * @var TestEntityWithDependency
39 | */
40 | #[ORM\JoinColumn(nullable: false)]
41 | #[ORM\OneToOne(targetEntity: \TestEntityWithDependency::class, cascade: ['all'])]
42 | protected $dependency = null;
43 |
44 | /**
45 | * Automatically adds a referenced entity on construction.
46 | */
47 | public function __construct()
48 | {
49 | $this->dependency = new TestEntityWithDependency();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Config/ConnectionConfiguration.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Config;
11 |
12 | /**
13 | * Represents a Doctrine database connection configuration.
14 | *
15 | * This class has been created to be able to use type hints for connection parameters
16 | * and to be able to provide pre-configured connection configurations (for example as
17 | * subclasses or via factory).
18 | *
19 | * Any connection parameters that are supported by Doctrine DBAL can be used in the configuration.
20 | *
21 | * @see http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html
22 | */
23 | class ConnectionConfiguration
24 | {
25 | /**
26 | * Connection parameters that are compatible to Doctrine DBAL.
27 | *
28 | * @var array
29 | */
30 | private $connectionParameters = null;
31 |
32 | /**
33 | * @param array $connectionParameters
34 | */
35 | public function __construct(array $connectionParameters)
36 | {
37 | $this->connectionParameters = $connectionParameters;
38 | }
39 |
40 | /**
41 | * Returns the connection parameters that are passed to Doctrine.
42 | *
43 | * @return array
44 | */
45 | public function getConnectionParameters()
46 | {
47 | return $this->connectionParameters;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Config/ExistingConnectionConfigurationTest.php:
--------------------------------------------------------------------------------
1 | connection = DriverManager::getConnection([
31 | 'driver' => 'pdo_sqlite',
32 | 'memory' => true,
33 | ]);
34 |
35 | $this->connectionConfiguration = new ExistingConnectionConfiguration($this->connection);
36 |
37 | parent::setUp();
38 | }
39 |
40 | public function testWorksWithInfrastructure()
41 | {
42 | $infrastructure = ORMInfrastructure::createOnlyFor(
43 | array(TestEntity::class),
44 | $this->connectionConfiguration
45 | );
46 |
47 | $infrastructure->import(new TestEntity());
48 |
49 | $this->assertEquals(1, $this->connection->fetchOne('SELECT COUNT(*) FROM test_entity'));
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/Inheritance/MappedSuperClassParentWithReference.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ReferencedEntity;
14 |
15 | /**
16 | * Mapped super class that references another entity.
17 | *
18 | * @see http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#mapped-superclasses
19 | */
20 | #[ORM\MappedSuperclass]
21 | abstract class MappedSuperClassParentWithReference
22 | {
23 | /**
24 | * A unique ID.
25 | *
26 | * @var integer|null
27 | */
28 | #[ORM\Id]
29 | #[ORM\Column(type: 'integer', name: 'id')]
30 | #[ORM\GeneratedValue]
31 | public $id = null;
32 |
33 | /**
34 | * Required reference to another entity.
35 | *
36 | * @var ReferencedEntity
37 | */
38 | #[ORM\JoinColumn(nullable: false)]
39 | #[ORM\OneToOne(targetEntity: \Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ReferencedEntity::class, cascade: ['all'])]
40 | protected $dependency = null;
41 |
42 | /**
43 | * Automatically creates a reference on construction.
44 | */
45 | public function __construct()
46 | {
47 | $this->dependency = new ReferencedEntity();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/Cascade/CascadePersistingEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Cascade;
11 |
12 | use Doctrine\Common\Collections\ArrayCollection;
13 | use Doctrine\Common\Collections\Collection;
14 | use Doctrine\ORM\Mapping as ORM;
15 |
16 | /**
17 | * Entity that automatically persists its associated entities.
18 | */
19 | #[ORM\Table(name: 'cascade_persist')]
20 | #[ORM\Entity]
21 | class CascadePersistingEntity
22 | {
23 | /**
24 | * A unique ID.
25 | *
26 | * @var integer|null
27 | */
28 | #[ORM\Id]
29 | #[ORM\Column(type: 'integer', name: 'id')]
30 | #[ORM\GeneratedValue]
31 | public $id = null;
32 |
33 | /**
34 | * @var Collection
35 | */
36 | #[ORM\OneToMany(targetEntity: \CascadePersistedEntity::class, mappedBy: 'parent', cascade: ['persist'])]
37 | private $associated;
38 |
39 | /**
40 | * Initializes the collection.
41 | */
42 | public function __construct()
43 | {
44 | $this->associated = new ArrayCollection();
45 | }
46 |
47 | /**
48 | * Adds the given entity to the persisting association.
49 | *
50 | * @param CascadePersistedEntity $entity
51 | */
52 | public function add(CascadePersistedEntity $entity)
53 | {
54 | $entity->parent = $this;
55 | $this->associated->add($entity);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog for `webfactory/doctrine-orm-test-infrastructure`
2 |
3 | This changelog tracks deprecations and changes breaking backwards compatibility. For more details on particular releases, consult the [GitHub releases page](https://github.com/webfactory/doctrine-orm-test-infrastructure/releases).
4 |
5 | # Version 2.0
6 |
7 | - The `ORMInfrastructure::create*()` methods by default read ORM mapping configuration through PHP attributes; annotations support has been removed in https://github.com/webfactory/doctrine-orm-test-infrastructure/pull/55/. You can, however, still create an instance of the `AnnotationDriver` mapping driver yourself (when using ORM 2.0) and pass it into these methods.
8 | - `\Webfactory\Doctrine\ORMTestInfrastructure\ORMInfrastructure::__construct()` is now a private method. Use the `::create*` methods to instantiate the `ORMInfrastructure`.
9 | - The `\Webfactory\Doctrine\ORMTestInfrastructure\Query::getExecutionTimeInSeconds()` method has been removed.
10 | - The `DetachingObjectManagerDecorator` and `MemorizingObjectManagerDecorator` classes have been removed.
11 |
12 | # Version 1.16
13 |
14 | - The `\Webfactory\Doctrine\ORMTestInfrastructure\Query::getExecutionTimeInSeconds()` method has been deprecated without replacement in https://github.com/webfactory/doctrine-orm-test-infrastructure/pull/52, to prepare for the removal of the `DebugStack` class in Doctrine DBAL 4.0.
15 | - Using annotation-based mapping as the default in `ORMInfrastructure::create*()` methods has been deprecated. Pass a mapping driver or upgrade `doctrine/orm` to >= 3.0 to switch to attributes-based mapping. Attributes-based configuration will be the default in the next major version.
16 |
--------------------------------------------------------------------------------
/src/ORMTestInfrastructure/Query.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\ORMTestInfrastructure;
11 |
12 | /**
13 | * Represents a query that has been executed.
14 | */
15 | class Query
16 | {
17 | /**
18 | * The SQL query.
19 | *
20 | * @var string
21 | */
22 | protected $sql = null;
23 |
24 | /**
25 | * The assigned parameters.
26 | *
27 | * @var mixed[]
28 | */
29 | protected $params = null;
30 |
31 | /**
32 | * Currently not used:
33 | * - types
34 | *
35 | * @param string $sql - sql
36 | * @param mixed[] $params - params
37 | */
38 | public function __construct($sql, array $params)
39 | {
40 | $this->sql = $sql;
41 | $this->params = $params;
42 | }
43 |
44 | /**
45 | * Returns the SQL of the query.
46 | *
47 | * @return string
48 | */
49 | public function getSql()
50 | {
51 | return $this->sql;
52 | }
53 |
54 | /**
55 | * Returns a list of parameters that have been assigned to the statement.
56 | *
57 | * @return mixed[]
58 | */
59 | public function getParams()
60 | {
61 | return $this->params;
62 | }
63 |
64 | /**
65 | * Returns a string representation of the query and its params.
66 | *
67 | * @return string
68 | */
69 | public function __toString()
70 | {
71 | $template = '"%s" with parameters [%s]';
72 | return sprintf($template, $this->getSql(), implode(', ', $this->getParams()));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/Fixtures/EntityNamespace1/Inheritance/ClassTableParentWithReferenceEntity.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance;
11 |
12 | use Doctrine\ORM\Mapping as ORM;
13 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ReferencedEntity;
14 |
15 | /**
16 | * Base class for entities with class table strategy.
17 | *
18 | * References another entity. This dependency is inherited by all children.
19 | *
20 | * @see http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#class-table-inheritance
21 | */
22 | #[ORM\Table(name: 'class_inheritance_with_reference_parent')]
23 | #[ORM\Entity]
24 | #[ORM\InheritanceType('JOINED')]
25 | #[ORM\DiscriminatorColumn(name: 'class', type: 'string')]
26 | abstract class ClassTableParentWithReferenceEntity
27 | {
28 | /**
29 | * A unique ID.
30 | *
31 | * @var integer|null
32 | */
33 | #[ORM\Id]
34 | #[ORM\Column(type: 'integer', name: 'id')]
35 | #[ORM\GeneratedValue]
36 | public $id = null;
37 |
38 | /**
39 | * Required reference to another entity.
40 | *
41 | * @var ReferencedEntity
42 | */
43 | #[ORM\JoinColumn(nullable: false)]
44 | #[ORM\OneToOne(targetEntity: \Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ReferencedEntity::class, cascade: ['all'])]
45 | protected $dependency = null;
46 |
47 | /**
48 | * Automatically creates a reference on construction.
49 | */
50 | public function __construct()
51 | {
52 | $this->dependency = new ReferencedEntity();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | schedule:
9 | - cron: '10 6 * * 1'
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | include:
18 | - { php-version: 8.1, orm-version: '', dependency-version: prefer-lowest }
19 | - { php-version: 8.3, orm-version: '^2.20', dependency-version: prefer-stable }
20 | - { php-version: 8.3, orm-version: '^3.0', dependency-version: prefer-stable }
21 | - { php-version: 8.4, orm-version: '^2.20', dependency-version: prefer-stable }
22 | - { php-version: 8.4, orm-version: '^3.0', dependency-version: prefer-stable }
23 | name: PHPUnit (PHP ${{matrix.php-version}}, Doctrine ORM version lock ${{ matrix.orm-version || 'none' }}, ${{ matrix.dependency-version }})
24 | steps:
25 | - uses: actions/checkout@v4
26 | - name: Install PHP
27 | uses: shivammathur/setup-php@v2
28 | with:
29 | php-version: ${{ matrix.php-version }}
30 | coverage: none
31 | - name: Lock Doctrine ORM version
32 | run: composer require --no-interaction --no-progress --no-suggest --no-scripts --ansi --no-update doctrine/orm '${{ matrix.orm-version }}'
33 | if: matrix.orm-version != ''
34 | - name: Install dependencies
35 | run: composer update --no-interaction --no-progress --no-suggest --no-scripts --ansi --${{ matrix.dependency-version}}
36 | - name: Display installed dependencies
37 | run: composer show
38 | - name: Run test suite
39 | run: vendor/bin/phpunit
40 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/QueryTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure;
11 |
12 | use PHPUnit\Framework\TestCase;
13 | use Webfactory\Doctrine\ORMTestInfrastructure\Query;
14 |
15 | /**
16 | * Tests the value object that holds query data.
17 | */
18 | class QueryTest extends TestCase
19 | {
20 | /**
21 | * System under test.
22 | *
23 | * @var Query
24 | */
25 | protected $query = null;
26 |
27 | /**
28 | * Initializes the test environment.
29 | */
30 | protected function setUp(): void
31 | {
32 | parent::setUp();
33 | $this->query= new Query(
34 | 'SELECT * FROM user WHERE id = ?',
35 | array(42)
36 | );
37 | }
38 |
39 | /**
40 | * Cleans up the test environment.
41 | */
42 | protected function tearDown(): void
43 | {
44 | $this->query = null;
45 | parent::tearDown();
46 | }
47 |
48 | /**
49 | * Checks if the correct SQL is returned by the query object.
50 | */
51 | public function testGetSqlReturnsCorrectValue()
52 | {
53 | $this->assertEquals('SELECT * FROM user WHERE id = ?', $this->query->getSql());
54 | }
55 |
56 | /**
57 | * Checks if the query parameters are returned correctly.
58 | */
59 | public function testGetParamsReturnsCorrectValue()
60 | {
61 | $this->assertEquals(array(42), $this->query->getParams());
62 | }
63 |
64 | /**
65 | * Checks if the query object can be used to retrieve a string representation of the query.
66 | */
67 | public function testQueryObjectProvidesStringRepresentation()
68 | {
69 | $this->assertNotEmpty((string)$this->query);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Config/ConnectionConfigurationTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\Config;
11 |
12 | use PHPUnit\Framework\TestCase;
13 | use Webfactory\Doctrine\Config\ConnectionConfiguration;
14 |
15 | class ConnectionConfigurationTest extends TestCase
16 | {
17 | /**
18 | * System under test.
19 | *
20 | * @var ConnectionConfiguration
21 | */
22 | private $connectionConfiguration = null;
23 |
24 | /**
25 | * Initializes the test environment.
26 | */
27 | protected function setUp(): void
28 | {
29 | parent::setUp();
30 | $this->connectionConfiguration = new ConnectionConfiguration(array(
31 | 'driver' => 'pdo_sqlite',
32 | 'user' => 'root',
33 | 'password' => '',
34 | 'memory' => true
35 | ));
36 | }
37 |
38 | /**
39 | * Cleans up the test environment.
40 | */
41 | protected function tearDown(): void
42 | {
43 | $this->connectionConfiguration = null;
44 | parent::tearDown();
45 | }
46 |
47 | public function testGetConnectionParametersReturnsProvidedValues()
48 | {
49 | $params = $this->connectionConfiguration->getConnectionParameters();
50 |
51 | $this->assertIsArray($params);
52 | $expectedParams = array(
53 | 'driver' => 'pdo_sqlite',
54 | 'user' => 'root',
55 | 'password' => '',
56 | 'memory' => true
57 | );
58 | foreach ($expectedParams as $param => $value) {
59 | $this->assertArrayHasKey($param, $params, 'Connection parameter missing.');
60 | $this->assertEquals($value, $params[$param], 'Unexpected value for connection parameter "' . $param . '".');
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Config/FileDatabaseConnectionConfiguration.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Config;
11 |
12 | /**
13 | * Specifies a connection to a file-based SQLite database.
14 | */
15 | class FileDatabaseConnectionConfiguration extends ConnectionConfiguration
16 | {
17 | /**
18 | * Creates a configuration that uses the given SQLite database file.
19 | *
20 | * Omit the file path to ensure that a temporary database file is created.
21 | *
22 | * @param string|null $databaseFilePath
23 | */
24 | public function __construct($databaseFilePath = null)
25 | {
26 | parent::__construct(array(
27 | 'driver' => 'pdo_sqlite',
28 | 'user' => 'root',
29 | 'password' => '',
30 | 'path' => $this->toDatabaseFilePath($databaseFilePath)
31 | ));
32 | }
33 |
34 | /**
35 | * Returns the path to the database file.
36 | *
37 | * The database file may not exist.
38 | *
39 | * @return \SplFileInfo
40 | */
41 | public function getDatabaseFile()
42 | {
43 | $parameters = $this->getConnectionParameters();
44 | return new \SplFileInfo($parameters['path']);
45 | }
46 |
47 | /**
48 | * Removes the database file if it exists.
49 | *
50 | * @return $this Provides a fluent interface.
51 | */
52 | public function cleanUp()
53 | {
54 | if ($this->getDatabaseFile()->isFile()) {
55 | unlink($this->getDatabaseFile()->getPathname());
56 | }
57 | return $this;
58 | }
59 |
60 | /**
61 | * Returns a file path for the database file.
62 | *
63 | * Generates a unique file name if the given $filePath is null.
64 | *
65 | * @param string|null $filePath
66 | * @return string
67 | */
68 | private function toDatabaseFilePath($filePath)
69 | {
70 | if ($filePath === null) {
71 | $temporaryFile = sys_get_temp_dir() . '/' . uniqid('db-', true) . '.sqlite';
72 | // Ensure that the temporary file is removed on shutdown, otherwise the filesystem
73 | // might be cluttered with database files.
74 | register_shutdown_function(array($this, 'cleanUp'));
75 | return $temporaryFile;
76 | }
77 | return $filePath;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/ORMTestInfrastructure/EntityListDriverDecorator.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\ORMTestInfrastructure;
11 |
12 | use Doctrine\Persistence\Mapping\ClassMetadata;
13 | use Doctrine\Persistence\Mapping\Driver\MappingDriver;
14 |
15 | /**
16 | * Driver decorator that restricts metadata access to a defined list of entities.
17 | *
18 | * @see https://github.com/webfactory/doctrine-orm-test-infrastructure/issues/11
19 | */
20 | class EntityListDriverDecorator implements MappingDriver
21 | {
22 | /**
23 | * The decorated driver.
24 | *
25 | * @var MappingDriver
26 | */
27 | protected $innerDriver = null;
28 |
29 | /**
30 | * Class names of all entities that are exposed.
31 | *
32 | * @var string[]
33 | */
34 | protected $exposedEntityClasses = null;
35 |
36 | /**
37 | * @param MappingDriver $innerDriver
38 | * @param string[] $exposedEntityClasses
39 | */
40 | public function __construct(MappingDriver $innerDriver, array $exposedEntityClasses)
41 | {
42 | $this->innerDriver = $innerDriver;
43 | $this->exposedEntityClasses = $this->normalizeClassNames($exposedEntityClasses);
44 | }
45 |
46 | /**
47 | * Gets the names of all mapped classes known to this driver.
48 | *
49 | * @return array The names of all mapped classes known to this driver.
50 | */
51 | public function getAllClassNames(): array
52 | {
53 | return array_intersect(
54 | $this->exposedEntityClasses,
55 | $this->innerDriver->getAllClassNames()
56 | );
57 | }
58 |
59 | /**
60 | * Loads the metadata for the specified class into the provided container.
61 | *
62 | * @param string $className
63 | * @param ClassMetadata $metadata
64 | */
65 | public function loadMetadataForClass($className, ClassMetadata $metadata): void
66 | {
67 | $this->innerDriver->loadMetadataForClass($className, $metadata);
68 | }
69 |
70 | /**
71 | * Returns whether the class with the specified name should have its metadata loaded.
72 | * This is only the case if it is either mapped as an Entity or a MappedSuperclass.
73 | *
74 | * @param string $className
75 | * @return boolean
76 | */
77 | public function isTransient($className): bool
78 | {
79 | return $this->innerDriver->isTransient($className);
80 | }
81 |
82 | /**
83 | * Removes leading slashes from the given class names.
84 | *
85 | * @param string[] $entityClasses
86 | * @return string[]
87 | */
88 | protected function normalizeClassNames(array $entityClasses)
89 | {
90 | return array_map(function ($class) {
91 | return ltrim($class, '\\');
92 | }, $entityClasses);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/ORMTestInfrastructure/ConfigurationFactory.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\ORMTestInfrastructure;
11 |
12 | use Doctrine\ORM\Mapping\Driver\AttributeDriver;
13 | use Doctrine\ORM\ORMSetup;
14 | use Doctrine\Persistence\Mapping\Driver\MappingDriver;
15 | use Psr\Cache\CacheItemPoolInterface;
16 | use Symfony\Component\Cache\Adapter\ArrayAdapter;
17 |
18 | /**
19 | * Creates ORM configurations for a set of entities.
20 | *
21 | * These configurations are meant for testing only.
22 | */
23 | class ConfigurationFactory
24 | {
25 | /** @var ?CacheItemPoolInterface */
26 | private static $metadataCache = null;
27 |
28 | /** @var ?MappingDriver */
29 | private $mappingDriver;
30 |
31 | public function __construct(?MappingDriver $mappingDriver = null)
32 | {
33 | $this->mappingDriver = $mappingDriver;
34 | }
35 |
36 | /**
37 | * Creates the ORM configuration for the given set of entities.
38 | *
39 | * @param string[] $entityClasses
40 | * @return \Doctrine\ORM\Configuration
41 | */
42 | public function createFor(array $entityClasses)
43 | {
44 | if (self::$metadataCache === null) {
45 | self::$metadataCache = new ArrayAdapter();
46 | }
47 |
48 | $mappingDriver = $this->mappingDriver ?? $this->createDefaultMappingDriver($entityClasses);
49 |
50 | $config = ORMSetup::createConfiguration(true, null, new ArrayAdapter());
51 | $config->setMetadataCache(self::$metadataCache);
52 | $config->setMetadataDriverImpl(new EntityListDriverDecorator($mappingDriver, $entityClasses));
53 |
54 | return $config;
55 | }
56 |
57 | /**
58 | * @param list $entityClasses
59 | *
60 | * @return MappingDriver
61 | */
62 | private function createDefaultMappingDriver(array $entityClasses)
63 | {
64 | $paths = $this->getDirectoryPathsForClassNames($entityClasses);
65 |
66 | return new AttributeDriver($paths);
67 | }
68 |
69 | /**
70 | * Returns a list of file paths for the provided class names.
71 | *
72 | * @param list $classNames
73 | * @return list
74 | */
75 | protected function getDirectoryPathsForClassNames(array $classNames)
76 | {
77 | $paths = array();
78 | foreach ($classNames as $className) {
79 | $paths[] = $this->getDirectoryPathForClassName($className);
80 | }
81 | return array_unique($paths);
82 | }
83 |
84 | /**
85 | * Returns the path to the directory that contains the given class.
86 | *
87 | * @param class-string $className
88 | * @return string
89 | */
90 | protected function getDirectoryPathForClassName($className)
91 | {
92 | $info = new \ReflectionClass($className);
93 | return dirname($info->getFileName());
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/EntityListDriverDecoratorTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure;
11 |
12 | use Doctrine\Persistence\Mapping\Driver\MappingDriver;
13 | use Doctrine\ORM\Mapping\ClassMetadata;
14 | use PHPUnit\Framework\MockObject\MockObject;
15 | use PHPUnit\Framework\TestCase;
16 | use Webfactory\Doctrine\ORMTestInfrastructure\EntityListDriverDecorator;
17 |
18 | class EntityListDriverDecoratorTest extends TestCase
19 | {
20 | /**
21 | * System under test.
22 | *
23 | * @var EntityListDriverDecorator
24 | */
25 | protected $driver = null;
26 |
27 | /**
28 | * The mocked, decorated driver.
29 | *
30 | * @var MappingDriver|MockObject
31 | */
32 | protected $innerDriver = null;
33 |
34 | /**
35 | * Initializes the test environment.
36 | */
37 | protected function setUp(): void
38 | {
39 | parent::setUp();
40 | $this->innerDriver = $this->createMock(MappingDriver::class);
41 | $this->driver = new EntityListDriverDecorator($this->innerDriver, array(
42 | 'My\Namespace\Person',
43 | 'My\Namespace\Address'
44 | ));
45 | }
46 |
47 | /**
48 | * Cleans up the test environment.
49 | */
50 | protected function tearDown(): void
51 | {
52 | $this->driver = null;
53 | $this->innerDriver = null;
54 | parent::tearDown();
55 | }
56 |
57 | public function testImplementsInterface()
58 | {
59 | $this->assertInstanceOf(MappingDriver::class, $this->driver);
60 | }
61 |
62 | public function testGetAllClassNamesReturnsOnlyExposedEntityClasses()
63 | {
64 | $this->innerDriver->expects($this->any())
65 | ->method('getAllClassNames')
66 | ->will($this->returnValue(array(
67 | 'My\Namespace\Person',
68 | 'My\Namespace\Address',
69 | 'My\Namespace\PhoneNumber'
70 | )));
71 |
72 | $classes = $this->driver->getAllClassNames();
73 |
74 | $this->assertIsArray($classes);
75 | $this->assertContains('My\Namespace\Person', $classes);
76 | $this->assertContains('My\Namespace\Address', $classes);
77 | $this->assertNotContains('My\Namespace\PhoneNumber', $classes);
78 | }
79 |
80 | /**
81 | * Ensures that the driver decorator does not expose entity classes, which are listed, but
82 | * not supported by the iner driver.
83 | */
84 | public function testDriverDoesNotExposeEntitiesThatAreInListButNotSupportedByInnerDriver()
85 | {
86 | $this->innerDriver->expects($this->any())
87 | ->method('getAllClassNames')
88 | ->will($this->returnValue(array(
89 | // The inner driver supports Person, but not Address.
90 | 'My\Namespace\Person'
91 | )));
92 |
93 | $classes = $this->driver->getAllClassNames();
94 |
95 | $this->assertIsArray($classes);
96 | $this->assertContains('My\Namespace\Person', $classes);
97 | $this->assertNotContains('My\Namespace\Address', $classes);
98 | }
99 |
100 | public function testGetAllClassNamesWorksIfEntityClassWasPassedWithLeadingBackslash()
101 | {
102 | $this->driver = new EntityListDriverDecorator($this->innerDriver, array(
103 | // The entity class is passed with a leading slash.
104 | '\My\Namespace\Person'
105 | ));
106 | $this->innerDriver->expects($this->any())
107 | ->method('getAllClassNames')
108 | ->will($this->returnValue(array(
109 | 'My\Namespace\Person'
110 | )));
111 |
112 | $classes = $this->driver->getAllClassNames();
113 |
114 | $this->assertIsArray($classes);
115 | $this->assertContains('My\Namespace\Person', $classes);
116 | }
117 |
118 | public function testDriverDelegatesMetadataCalls()
119 | {
120 | $this->innerDriver->expects($this->once())
121 | ->method('loadMetadataForClass');
122 |
123 | $this->driver->loadMetadataForClass('My\Namespace\Person', new ClassMetadata('My\Namespace\Person'));
124 | }
125 |
126 | public function testDriverDelegatesIsTransientCall()
127 | {
128 | $this->innerDriver->expects($this->once())
129 | ->method('isTransient')
130 | ->willReturn(false);
131 |
132 | $this->driver->isTransient('My\Namespace\Person');
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/tests/Config/FileDatabaseConnectionConfigurationTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\Config;
11 |
12 | use PHPUnit\Framework\TestCase;
13 | use Webfactory\Doctrine\Config\ConnectionConfiguration;
14 | use Webfactory\Doctrine\Config\FileDatabaseConnectionConfiguration;
15 | use Webfactory\Doctrine\ORMTestInfrastructure\ORMInfrastructure;
16 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\TestEntity;
17 |
18 | class FileDatabaseConnectionConfigurationTest extends TestCase
19 | {
20 | public function testKeepsProvidedFilePath()
21 | {
22 | $path = __DIR__ . '/_files/my-db.sqlite';
23 | $configuration = new FileDatabaseConnectionConfiguration($path);
24 |
25 | $file = $configuration->getDatabaseFile();
26 | $this->assertInstanceOf('SplFileInfo', $file);
27 | $this->assertEquals($path, $file->getPathname());
28 | }
29 |
30 | public function testGeneratedFileNameIsNotChangedForExistingConfigurationObject()
31 | {
32 | $configuration = new FileDatabaseConnectionConfiguration();
33 |
34 | $firstCall = $configuration->getDatabaseFile();
35 | $secondCall = $configuration->getDatabaseFile();
36 | $this->assertInstanceOf('SplFileInfo', $firstCall);
37 | $this->assertInstanceOf('SplFileInfo', $secondCall);
38 | $this->assertEquals($firstCall->getPathname(), $secondCall->getPathname());
39 | }
40 |
41 | public function testGeneratesUniqueFileNameIfFilePathIsOmitted()
42 | {
43 | $firstConfiguration = new FileDatabaseConnectionConfiguration();
44 | $secondConfiguration = new FileDatabaseConnectionConfiguration();
45 |
46 | $firstFile = $firstConfiguration->getDatabaseFile();
47 | $secondFile = $secondConfiguration->getDatabaseFile();
48 | $this->assertInstanceOf('SplFileInfo', $firstFile);
49 | $this->assertInstanceOf('SplFileInfo', $secondFile);
50 | $this->assertNotEquals($firstFile->getPathname(), $secondFile->getPathname());
51 | }
52 |
53 | public function testCleanUpRemovesTheDatabaseFileIfItExists()
54 | {
55 | $configuration = new FileDatabaseConnectionConfiguration();
56 | $file = $configuration->getDatabaseFile();
57 | $this->assertInstanceOf('SplFileInfo', $file);
58 | touch($file->getPathname());
59 |
60 | $configuration->cleanUp();
61 |
62 | $this->assertFileDoesNotExist($file->getPathname());
63 | }
64 |
65 | public function testCleanUpDoesNothingIfTheDatabaseFileDoesNotExistYet()
66 | {
67 | $configuration = new FileDatabaseConnectionConfiguration();
68 | $file = $configuration->getDatabaseFile();
69 | $this->assertInstanceOf('SplFileInfo', $file);
70 |
71 | $this->assertFileDoesNotExist($file->getPathname());
72 |
73 | $configuration->cleanUp();
74 | }
75 |
76 | public function testCleanUpProvidesFluentInterface()
77 | {
78 | $configuration = new FileDatabaseConnectionConfiguration();
79 |
80 | $this->assertSame($configuration, $configuration->cleanUp());
81 | }
82 |
83 | /**
84 | * Checks if the connection configuration *really* works with the infrastructure.
85 | */
86 | public function testWorksWithInfrastructure()
87 | {
88 | $configuration = new FileDatabaseConnectionConfiguration();
89 | $infrastructure = $this->createInfrastructure($configuration);
90 |
91 | $this->assertNull(
92 | $infrastructure->import(new TestEntity())
93 | );
94 | }
95 |
96 | public function testDatabaseFileIsCreated()
97 | {
98 | $configuration = new FileDatabaseConnectionConfiguration();
99 |
100 | $infrastructure = $this->createInfrastructure($configuration);
101 | $infrastructure->import(new TestEntity());
102 |
103 | $file = $configuration->getDatabaseFile();
104 | $this->assertInstanceOf('SplFileInfo', $file);
105 | $this->assertFileExists($file->getPathname());
106 | }
107 |
108 | /**
109 | * Creates a new infrastructure with the given connection configuration.
110 | *
111 | * @param ConnectionConfiguration $configuration
112 | * @return ORMInfrastructure
113 | */
114 | private function createInfrastructure(ConnectionConfiguration $configuration)
115 | {
116 | $infrastructure = ORMInfrastructure::createOnlyFor(
117 | array(
118 | TestEntity::class
119 | ),
120 | $configuration
121 | );
122 |
123 | return $infrastructure;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/ORMTestInfrastructure/Importer.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\ORMTestInfrastructure;
11 |
12 | use Doctrine\Persistence\ObjectManager;
13 | use Doctrine\ORM\EntityManagerInterface;
14 |
15 | /**
16 | * Helper class that is used to import entities via entity manager.
17 | */
18 | class Importer
19 | {
20 |
21 | /**
22 | * The entity manager that is used to add the imported entities.
23 | *
24 | * @var \Doctrine\ORM\EntityManagerInterface
25 | */
26 | protected $entityManager = null;
27 |
28 | /**
29 | * Creates an importer that uses the provided entity manager.
30 | *
31 | * @param EntityManagerInterface $entityManager
32 | */
33 | public function __construct(EntityManagerInterface $entityManager)
34 | {
35 | $this->entityManager = $entityManager;
36 | }
37 |
38 | /**
39 | * Imports entities from the provided data source.
40 | *
41 | * The importer supports several ways to add entities to the database.
42 | * In any case the importer handles necessary flush() calls, therefore,
43 | * manual flushing is not necessary.
44 | *
45 | * # Callbacks #
46 | *
47 | * Callbacks are executed and receive an object manager as argument:
48 | *
49 | * $loader = function (\Doctrine\Common\Persistence\ObjectManager $objectManager) {
50 | * $objectManager->persist(new MyEntity());
51 | * $objectManager->persist(new MyEntity());
52 | * }
53 | * $importer->import($loader);
54 | *
55 | * Please note, that an object manager and not the original entity manager is passed.
56 | *
57 | * # Single entities and lists of entities #
58 | *
59 | * Single entities and lists of entities are automatically persisted:
60 | *
61 | * $importer->import(new MyEntity());
62 | * $importer->import(array(new MyEntity(), new MyEntity()));
63 | *
64 | * # Files #
65 | *
66 | * To create re-usable data sets entities can be imported from PHP files:
67 | *
68 | * $importer->import('/path/to/data/set.php');
69 | *
70 | * The imported file has access to the global variable $objectManager, which
71 | * can be used to persist the entities:
72 | *
73 | * persist(new MyEntity());
76 | * $objectManager->persist(new MyEntity());
77 | *
78 | * Alternatively, the file can return an array of entities that must be persisted.
79 | * This avoids the dependency on the global $objectManager variable:
80 | *
81 | * importFromCallback($dataSource);
95 | return;
96 | }
97 | if (is_object($dataSource)) {
98 | $this->importEntity($dataSource);
99 | return;
100 | }
101 | if (is_array($dataSource)) {
102 | $this->importEntityList($dataSource);
103 | return;
104 | }
105 | if (is_file($dataSource)) {
106 | $this->importFromFile($dataSource);
107 | return;
108 | }
109 | $message = 'Cannot handle data source of type "' . gettype($dataSource) . '".';
110 | throw new \InvalidArgumentException($message);
111 | }
112 |
113 | /**
114 | * Imports a single entity.
115 | *
116 | * @param object $entity
117 | */
118 | protected function importEntity($entity)
119 | {
120 | $this->importEntityList(array($entity));
121 | }
122 |
123 | /**
124 | * Imports a list of entities.
125 | *
126 | * @param object[] $entities
127 | */
128 | protected function importEntityList(array $entities)
129 | {
130 | $this->importFromCallback(function (ObjectManager $objectManager) use ($entities) {
131 | foreach ($entities as $entity) {
132 | /* @var $entity object */
133 | $objectManager->persist($entity);
134 | }
135 | });
136 | }
137 |
138 | /**
139 | * Imports entities from a PHP file.
140 | *
141 | * @param string $path
142 | */
143 | protected function importFromFile($path)
144 | {
145 | $entities = null;
146 | /* @noinspection PhpUnusedParameterInspection $objectManager should be in the scope of the included file. */
147 | $this->importFromCallback(function (ObjectManager $objectManager) use ($path, &$entities) {
148 | $entities = include $path;
149 | });
150 | if (is_array($entities)) {
151 | // Persist entities that were returned by the file.
152 | $this->importEntityList($entities);
153 | }
154 | }
155 |
156 | /**
157 | * Uses the provided callback to import entities.
158 | *
159 | * @param callable $callback
160 | */
161 | protected function importFromCallback($callback)
162 | {
163 | $this->entityManager->wrapInTransaction(function (ObjectManager $objectManager) use ($callback) {
164 | call_user_func($callback, $objectManager);
165 | });
166 |
167 | // Clear the entity manager to ensure that there are no leftovers in the identity map.
168 | $this->entityManager->clear();
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | doctrine-orm-test-infrastructure
2 | ================================
3 |
4 | ⚠️ This library is not receiving active maintenance anymore and is likely to be abandoned in the near future.
5 |
6 | There have been major changes in the current Doctrine DBAL 3.x releases that make it increasingly hard to maintain
7 | helper classes that create a working ORM configuration "out of nothing". At the same time, we have been using
8 | the functionality provided here only to limited extent in our own projects. Most of the time, Symfony functional
9 | tests together with Zenstruck Foundry and testing against the real MySQL schema instead of an in-memory SQLite schema
10 | created ad hoc turned out to be good enough.
11 |
12 | ---
13 |
14 | 
15 |
16 | This library provides some infrastructure for tests of Doctrine ORM entities, featuring:
17 |
18 | - configuration of a SQLite in memory database, compromising well between speed and a database environment being both
19 | realistic and isolated
20 | - a mechanism for importing fixtures into your database that circumvents Doctrine's caching. This results in a more
21 | realistic test environment when loading entities from a repository.
22 |
23 | [We](https://www.webfactory.de/) use it to test Doctrine repositories and entities in Symfony applications. It's a
24 | lightweight alternative to the
25 | heavyweight [functional tests suggested in the Symfony documentation](http://symfony.com/doc/current/cookbook/testing/doctrine.html)
26 | (we don't suggest you should skip those - we just want to open another path).
27 |
28 | In non-application bundles, where functional tests are not possible,
29 | it is our only way to test repositories and entities.
30 |
31 | Installation
32 | ------------
33 |
34 | Install via composer (see http://getcomposer.org/):
35 |
36 | composer require --dev webfactory/doctrine-orm-test-infrastructure
37 |
38 | Usage
39 | -----
40 |
41 | ```php
42 | infrastructure = ORMInfrastructure::createWithDependenciesFor(MyEntity::class);
63 | $this->repository = $this->infrastructure->getRepository(MyEntity::class);
64 | }
65 |
66 | /**
67 | * Example test: Asserts imported fixtures are retrieved with findAll().
68 | */
69 | public function testFindAllRetrievesFixtures(): void
70 | {
71 | $myEntityFixture = new MyEntity();
72 |
73 | $this->infrastructure->import($myEntityFixture);
74 | $entitiesLoadedFromDatabase = $this->repository->findAll();
75 |
76 | /*
77 | import() will use a dedicated entity manager, so imported entities do not
78 | end up in the identity map. But this also means loading entities from the
79 | database will create _different object instances_.
80 |
81 | So, this does not hold:
82 | */
83 | // self::assertContains($myEntityFixture, $entitiesLoadedFromDatabase);
84 |
85 | // But you can do things like this (you probably want to extract that in a convenient assertion method):
86 | self::assertCount(1, $entitiesLoadedFromDatabase);
87 | $entityLoadedFromDatabase = $entitiesLoadedFromDatabase[0];
88 | self::assertSame($myEntityFixture->getId(), $entityLoadedFromDatabase->getId());
89 | }
90 |
91 | /**
92 | * Example test for retrieving Doctrine's entity manager.
93 | */
94 | public function testSomeFancyThingWithEntityManager(): void
95 | {
96 | $entityManager = $this->infrastructure->getEntityManager();
97 | // ...
98 | }
99 | }
100 | ```
101 |
102 | Migrating to attribute-based mapping configuration (with version 1.x)
103 | ---------------------------------------------------------------------
104 |
105 | In versions 1.x of this library, the `ORMInfrastructure::createWithDependenciesFor()` and `ORMInfrastructure::createOnlyFor()` methods
106 | by default assume that the Doctrine ORM mapping is provided through annotations. Annotations-based configuration is no supported anymore in ORM 3.0.
107 |
108 | To allow for a seamless transition towards attribute-based or other types of mapping, a mapping driver can be passed
109 | when creating instances of the `ORMInfrastructure`.
110 |
111 | If you wish to switch to attribute-based mappings, pass a `new \Doctrine\ORM\Mapping\Driver\AttributeDriver($paths)`,
112 | where `$paths` is an array of directory paths where your entity classes are stored.
113 |
114 | For hybrid (annotations and attributes) mapping configurations, you can use `\Doctrine\Persistence\Mapping\Driver\MappingDriverChain`.
115 | Multiple mapping drivers can be registered on the driver chain by providing namespace prefixes. For every namespace prefix,
116 | only one mapping driver can be used.
117 |
118 | Starting in version 2.0.0, attributes-based mapping will be the default.
119 |
120 | Testing the library itself
121 | --------------------------
122 |
123 | After installing the dependencies managed via composer, just run
124 |
125 | vendor/bin/phpunit
126 |
127 | from the library's root folder. This uses the shipped phpunit.xml.dist - feel free to create your own phpunit.xml if you
128 | need local changes.
129 |
130 | Happy testing!
131 |
132 | Known Issues
133 | ------------
134 |
135 | Please note that apart from any [open issues in this library](https://github.com/webfactory/doctrine-orm-test-infrastructure/issues), you
136 | may stumble upon any Doctrine issues. Especially take care of
137 | it's [known sqlite issues](http://doctrine-dbal.readthedocs.org/en/latest/reference/known-vendor-issues.html#sqlite).
138 |
139 | Credits, Copyright and License
140 | ------------------------------
141 |
142 | This package was first written by webfactory GmbH (Bonn, Germany) and received [contributions
143 | from other people](https://github.com/webfactory/doctrine-orm-test-infrastructure/graphs/contributors) since then.
144 |
145 | webfactory is a software development agency with a focus on PHP (mostly [Symfony](http://github.com/symfony/symfony)).
146 | If you're a developer looking for new challenges, we'd like to hear from you!
147 |
148 | -
149 |
150 | Copyright 2012 – 2024 webfactory GmbH, Bonn. Code released under [the MIT license](LICENSE).
151 |
--------------------------------------------------------------------------------
/src/ORMTestInfrastructure/EntityDependencyResolver.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\ORMTestInfrastructure;
11 |
12 | use Doctrine\ORM\Configuration;
13 | use Doctrine\ORM\Mapping\ClassMetadata;
14 | use Doctrine\Persistence\Mapping\Driver\MappingDriver;
15 | use Doctrine\Persistence\Mapping\ReflectionService;
16 | use Doctrine\Persistence\Mapping\RuntimeReflectionService;
17 |
18 | /**
19 | * Takes a set of entity classes and resolves to a set that contains all entities
20 | * that are referenced by the provided entity classes (via associations).
21 | *
22 | * The resolved set also includes the original entity classes.
23 | */
24 | class EntityDependencyResolver implements \IteratorAggregate
25 | {
26 | /**
27 | * Contains the names of the entity classes that were initially provided.
28 | *
29 | * @var string[]
30 | */
31 | protected $initialEntitySet = null;
32 |
33 | /**
34 | * Service that is used to inspect entity classes.
35 | *
36 | * @var ReflectionService
37 | */
38 | protected $reflectionService = null;
39 |
40 | /**
41 | * Factory that is used to create ORM configurations.
42 | *
43 | * @var ConfigurationFactory
44 | */
45 | protected $configFactory = null;
46 |
47 | /**
48 | * Creates a resolver for the given entity classes.
49 | *
50 | * @param string[] $entityClasses
51 | */
52 | public function __construct(array $entityClasses, ?MappingDriver $mappingDriver = null)
53 | {
54 | $this->initialEntitySet = $this->normalizeClassNames($entityClasses);
55 | $this->reflectionService = new RuntimeReflectionService();
56 | $this->configFactory = new ConfigurationFactory($mappingDriver);
57 | }
58 |
59 | /**
60 | * Allows iterating over the set of resolved entities.
61 | *
62 | * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
63 | */
64 | public function getIterator(): \Traversable
65 | {
66 | return new \ArrayIterator($this->resolve($this->initialEntitySet));
67 | }
68 |
69 | /**
70 | * Resolves the dependencies for the given entities.
71 | *
72 | * @param string[] $entityClasses
73 | * @return string[]
74 | */
75 | protected function resolve(array $entityClasses)
76 | {
77 | $entitiesToCheck = $entityClasses;
78 | $config = $this->configFactory->createFor($entitiesToCheck);
79 | while (count($associatedEntities = $this->getDirectlyAssociatedEntities($config, $entitiesToCheck)) > 0) {
80 | $associatedEntities = $this->removeInterfaces($associatedEntities);
81 | $newAssociations = array_diff($associatedEntities, $entityClasses);
82 | $entityClasses = array_merge($entityClasses, $newAssociations);
83 | $config = $this->configFactory->createFor($entityClasses);
84 | $entitiesToCheck = $newAssociations;
85 | }
86 | return $entityClasses;
87 | }
88 |
89 | /**
90 | * Returns the class names of additional entities that are directly associated with
91 | * one of the entities that is explicitly mentioned in the given configuration.
92 | *
93 | * @param Configuration $config
94 | * @param string[] $entityClasses Classes whose associations are checked.
95 | * @return string[] Associated entity classes.
96 | */
97 | protected function getDirectlyAssociatedEntities(Configuration $config, $entityClasses)
98 | {
99 | if (count($entityClasses) === 0) {
100 | return array();
101 | }
102 | $associatedEntities = [];
103 | $mappingDriver = $config->getMetadataDriverImpl();
104 |
105 | foreach ($entityClasses as $entityClass) {
106 | /* @var $entityClass string */
107 | $metadata = new ClassMetadata($entityClass);
108 | $metadata->initializeReflection($this->reflectionService);
109 | $mappingDriver->loadMetadataForClass($entityClass, $metadata);
110 |
111 | foreach ($metadata->getAssociationNames() as $name) {
112 | /* @var $name string */
113 | $associatedEntity = $metadata->getAssociationTargetClass($name);
114 | $associatedEntities[$metadata->fullyQualifiedClassName($associatedEntity)] = true;
115 | }
116 |
117 | if ($metadata->isInheritanceTypeJoined()) {
118 | foreach ($metadata->discriminatorMap as $childClass) {
119 | $associatedEntities[$childClass] = true;
120 | }
121 | }
122 |
123 | // Add parent classes that are involved in some kind of entity inheritance.
124 | $parentClassesTowardsInheritanceBaseTable = [];
125 | foreach ($this->reflectionService->getParentClasses($entityClass) as $parentClass) {
126 | if ($mappingDriver->isTransient($parentClass)) {
127 | continue;
128 | }
129 |
130 | $parentClassesTowardsInheritanceBaseTable[] = $parentClass;
131 | $metadata = new ClassMetadata($parentClass);
132 | $metadata->initializeReflection($this->reflectionService);
133 | $mappingDriver->loadMetadataForClass($parentClass, $metadata);
134 | if ($metadata->isInheritanceTypeNone()) {
135 | continue;
136 | }
137 |
138 | foreach ($parentClassesTowardsInheritanceBaseTable as $class) {
139 | $associatedEntities[$class] = true;
140 | }
141 | $parentClassesTowardsInheritanceBaseTable = [];
142 | }
143 | }
144 | return array_keys($associatedEntities);
145 | }
146 |
147 | /**
148 | * Removes leading slashes from the given class names.
149 | *
150 | * @param string[] $entityClasses
151 | * @return string[]
152 | */
153 | protected function normalizeClassNames(array $entityClasses)
154 | {
155 | return array_map(function ($class) {
156 | return ltrim($class, '\\');
157 | }, $entityClasses);
158 | }
159 |
160 | /**
161 | * Returns all interfaces from the given list of entity types.
162 | *
163 | * Interfaces can be defined as association targets, but this simple resolver cannot handle them properly.
164 | * Interfaces need additional configuration to be resolved to real entity classes.
165 | *
166 | * @param string[] $entityTypes
167 | * @return string[]
168 | */
169 | private function removeInterfaces($entityTypes)
170 | {
171 | return array_filter(
172 | $entityTypes,
173 | function ($entity) {
174 | return !interface_exists($entity);
175 | }
176 | );
177 | }
178 |
179 | private function fetchMetadata(string $entityClass, MappingDriver $mappingDriver): ClassMetadata
180 | {
181 | $metadata = new ClassMetadata($entityClass);
182 | $metadata->initializeReflection($this->reflectionService);
183 | $mappingDriver->loadMetadataForClass($entityClass, $metadata);
184 |
185 | return $metadata;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/ImporterTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure;
11 |
12 | use Doctrine\ORM\EntityManager;
13 | use Doctrine\Persistence\ObjectManager;
14 | use PHPUnit\Framework\MockObject\MockObject;
15 | use PHPUnit\Framework\TestCase;
16 | use Webfactory\Doctrine\ORMTestInfrastructure\Importer;
17 |
18 | /**
19 | * Tests the importer.
20 | */
21 | class ImporterTest extends TestCase
22 | {
23 | /**
24 | * System under test.
25 | *
26 | * @var Importer
27 | */
28 | protected $importer = null;
29 |
30 | /**
31 | * The (mocked) entity manager.
32 | *
33 | * @var \Doctrine\ORM\EntityManager|MockObject
34 | */
35 | protected $entityManager = null;
36 |
37 | /**
38 | * Initializes the test environment.
39 | */
40 | protected function setUp(): void
41 | {
42 | parent::setUp();
43 | $this->entityManager = $this->createEntityManager();
44 | $this->importer = new Importer($this->entityManager);
45 | }
46 |
47 | /**
48 | * Cleans up the test environment.
49 | */
50 | protected function tearDown(): void
51 | {
52 | $this->importer = null;
53 | $this->entityManager = null;
54 | parent::tearDown();
55 | }
56 |
57 | /**
58 | * Checks if import() passes an object manager to a provided callback.
59 | *
60 | * Only an object manager instance, not the original entity manager is
61 | * expected as the importer may decide to uses a decorator.
62 | */
63 | public function testImportPassesObjectManagerToCallback()
64 | {
65 | $callable = new class {
66 | public $invoked = false;
67 | public function __invoke(ObjectManager $om) {
68 | $this->invoked = true;
69 | }
70 | };
71 |
72 | $this->importer->import($callable);
73 | $this->assertTrue($callable->invoked);
74 | }
75 |
76 | /**
77 | * Checks if persist() calls from a callable are delegated to the entity manager.
78 | */
79 | public function testEntitiesFromCallableArePersisted()
80 | {
81 | $callable = function (ObjectManager $objectManager) {
82 | $objectManager->persist(new \stdClass());
83 | $objectManager->persist(new \stdClass());
84 | };
85 |
86 | $this->entityManager->expects($this->exactly(2))
87 | ->method('persist')
88 | ->with($this->isInstanceOf(\stdClass::class));
89 |
90 | $this->assertNull(
91 | $this->importer->import($callable)
92 | );
93 | }
94 |
95 | /**
96 | * Checks if the importer accepts a file to persist entities.
97 | */
98 | public function testImportAddsEntitiesFromFile()
99 | {
100 | $this->entityManager->expects($this->exactly(2))
101 | ->method('persist')
102 | ->with($this->isInstanceOf(\stdClass::class));
103 |
104 | $path = __DIR__ . '/Fixtures/Importer/LoadEntities.php';
105 | $this->assertNull(
106 | $this->importer->import($path)
107 | );
108 | }
109 |
110 | /**
111 | * Ensures that entities, which are returned by a file, are persisted by the importer.
112 | */
113 | public function testImportAddsEntitiesThatAreReturnedFromFile()
114 | {
115 | $this->entityManager->expects($this->exactly(2))
116 | ->method('persist')
117 | ->with($this->isInstanceOf(\stdClass::class));
118 |
119 | $path = __DIR__ . '/Fixtures/Importer/ReturnEntities.php';
120 | $this->assertNull(
121 | $this->importer->import($path)
122 | );
123 | }
124 |
125 | /**
126 | * Checks if import() persists a single entity.
127 | */
128 | public function testImportPersistsSingleEntity()
129 | {
130 | $this->entityManager->expects($this->once())
131 | ->method('persist')
132 | ->with($this->isInstanceOf(\stdClass::class));
133 |
134 | $this->assertNull(
135 | $this->importer->import(new \stdClass())
136 | );
137 | }
138 |
139 | /**
140 | * Ensures that import persists an array of entities.
141 | */
142 | public function testImportPersistsArrayOfEntities()
143 | {
144 | $this->entityManager->expects($this->exactly(2))
145 | ->method('persist')
146 | ->with($this->isInstanceOf(\stdClass::class));
147 |
148 | $entities = array(
149 | new \stdClass(),
150 | new \stdClass()
151 | );
152 | $this->assertNull(
153 | $this->importer->import($entities)
154 | );
155 | }
156 |
157 | public function testImportCanHandleEmptyEntityList()
158 | {
159 | $this->assertNull(
160 | $this->importer->import([])
161 | );
162 | }
163 |
164 | /**
165 | * Ensures that import() throws an exception if the given data source
166 | * is not supported.
167 | */
168 | public function testImportThrowsExceptionIfDataSourceIsNotSupported()
169 | {
170 | $this->expectException(\InvalidArgumentException::class);
171 | $this->importer->import(42);
172 | }
173 |
174 | /**
175 | * Checks if the importer clears the manager after import.
176 | */
177 | public function testImporterClearsEntityManager()
178 | {
179 | $this->entityManager->expects($this->once())
180 | ->method('clear');
181 |
182 | $entities = array(
183 | new \stdClass(),
184 | new \stdClass()
185 | );
186 | $this->assertNull(
187 | $this->importer->import($entities)
188 | );
189 | }
190 |
191 | public function testEntityManagerIsFlushedOnlyOnce()
192 | {
193 | $this->entityManager->expects($this->once())
194 | ->method('flush');
195 |
196 | $entities = array(
197 | new \stdClass()
198 | );
199 | $this->assertNull(
200 | $this->importer->import($entities)
201 | );
202 | }
203 |
204 | public function testEntityManagerIsClearedAfterFlush()
205 | {
206 | $cleared = 0.0;
207 | $callCounter = 0;
208 | $this->entityManager->expects($this->once())
209 | ->method('clear')
210 | ->will($this->returnCallback(function () use (&$cleared, &$callCounter) {
211 | $cleared = $callCounter++;
212 | }));
213 | $flushed = 0.0;
214 | $this->entityManager->expects($this->once())
215 | ->method('flush')
216 | ->will($this->returnCallback(function () use (&$flushed, &$callCounter) {
217 | $flushed = $callCounter++;
218 | }));
219 |
220 | $entities = array(
221 | new \stdClass()
222 | );
223 | $this->importer->import($entities);
224 |
225 | $this->assertGreaterThan($flushed, $cleared, 'clear() was called before flush().');
226 | }
227 |
228 | /**
229 | * Creates a mocked entity manager.
230 | *
231 | * @return \Doctrine\ORM\EntityManagerInterface|MockObject
232 | */
233 | protected function createEntityManager()
234 | {
235 | $mock = $this->createMock(EntityManager::class);
236 | // Simulates the transactional() call on the entity manager.
237 | $transactional = function ($callback) use ($mock) {
238 | /* @var $mock \Doctrine\ORM\EntityManagerInterface */
239 | $result = call_user_func($callback, $mock);
240 | $mock->flush();
241 | return $result ?: true;
242 | };
243 | $mock->expects($this->any())
244 | ->method('wrapInTransaction')
245 | ->will($this->returnCallback($transactional));
246 | return $mock;
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/EntityDependencyResolverTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure;
11 |
12 | use PHPUnit\Framework\TestCase;
13 | use Webfactory\Doctrine\ORMTestInfrastructure\EntityDependencyResolver;
14 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ChainReferenceEntity;
15 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance\ClassTableChildEntity;
16 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance\ClassTableChildWithParentReferenceEntity;
17 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance\ClassTableParentEntity;
18 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance\DiscriminatorMapChildEntity;
19 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance\DiscriminatorMapEntity;
20 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Inheritance\MappedSuperClassChild;
21 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\InterfaceAssociation\EntityInterface;
22 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\InterfaceAssociation\EntityWithAssociationAgainstInterface;
23 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ReferenceCycleEntity;
24 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ReferencedEntity;
25 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\TestEntity;
26 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\TestEntityWithDependency;
27 |
28 | /**
29 | * Tests the entity resolver.
30 | */
31 | class EntityDependencyResolverTest extends TestCase
32 | {
33 | /**
34 | * Ensures that the resolver is traversable.
35 | */
36 | public function testResolverIsTraversable()
37 | {
38 | $resolver = new EntityDependencyResolver(array(
39 | TestEntity::class
40 | ));
41 |
42 | $this->assertInstanceOf(\Traversable::class, $resolver);
43 | }
44 |
45 | /**
46 | * Checks if the resolved set contains the initially provided entity classes.
47 | */
48 | public function testSetContainsProvidedEntityClasses()
49 | {
50 | $resolver = new EntityDependencyResolver(array(
51 | TestEntity::class
52 | ));
53 |
54 | $this->assertContainsEntity(
55 | TestEntity::class,
56 | $resolver
57 | );
58 | }
59 |
60 | /**
61 | * Checks if entities that are directly associated to the initially provided entities
62 | * are contained in the resolved set.
63 | */
64 | public function testSetContainsEntityClassesThatAreDirectlyConnectedToInitialSet()
65 | {
66 | $resolver = new EntityDependencyResolver(array(
67 | TestEntityWithDependency::class
68 | ));
69 |
70 | $this->assertContainsEntity(
71 | ReferencedEntity::class,
72 | $resolver
73 | );
74 | }
75 |
76 | /**
77 | * Ensures that entities, which are connected via other associated entities,
78 | * are contained in the generated set.
79 | *
80 | * Example:
81 | *
82 | * A -> B -> C
83 | */
84 | public function testSetContainsIndirectlyConnectedEntityClasses()
85 | {
86 | $resolver = new EntityDependencyResolver(array(
87 | ChainReferenceEntity::class
88 | ));
89 |
90 | $this->assertContainsEntity(
91 | ReferencedEntity::class,
92 | $resolver
93 | );
94 | }
95 |
96 | /**
97 | * Ensures that the resolver can handle dependency cycles.
98 | */
99 | public function testResolverCanHandleDependencyCycles()
100 | {
101 | $resolver = new EntityDependencyResolver(array(
102 | ReferenceCycleEntity::class
103 | ));
104 |
105 | $this->assertContainsEntity(
106 | ReferenceCycleEntity::class,
107 | $resolver
108 | );
109 | }
110 |
111 | /**
112 | * Ensures that the resolved entity list contains each entity class only once.
113 | */
114 | public function testSetContainsEntitiesOnlyOnce()
115 | {
116 | $resolver = new EntityDependencyResolver(array(
117 | ReferenceCycleEntity::class
118 | ));
119 |
120 | $resolvedSet = $this->getResolvedSet($resolver);
121 |
122 | $normalized = array_unique($resolvedSet);
123 | sort($resolvedSet);
124 | sort($normalized);
125 | $this->assertEquals($normalized, $resolvedSet);
126 | }
127 |
128 | /**
129 | * Ensures that the resolver returns the entity class names without leading slash.
130 | */
131 | public function testResolvedSetContainsEntityClassesWithoutLeadingSlash()
132 | {
133 | $resolver = new EntityDependencyResolver(array(
134 | ChainReferenceEntity::class
135 | ));
136 |
137 | $resolvedSet = $this->getResolvedSet($resolver);
138 |
139 | foreach ($resolvedSet as $entityClass) {
140 | /* @var $entityClass string */
141 | $message = 'Entity class name must be normalized and must not start with \\.';
142 | $this->assertStringStartsNotWith('\\', $entityClass, $message);
143 | }
144 | }
145 |
146 | /**
147 | * Ensures that a parent that uses class table inheritance is listed in the resolved set.
148 | */
149 | public function testResolvedSetContainsNameOfClassTableInheritanceParent()
150 | {
151 | $resolver = new EntityDependencyResolver(array(
152 | ClassTableChildEntity::class
153 | ));
154 |
155 | $this->assertContainsEntity(
156 | ClassTableParentEntity::class,
157 | $resolver
158 | );
159 | }
160 |
161 | /**
162 | * Ensures that the resolved set contains an entity class that is referenced by a parent
163 | * entity (with class table inheritance strategy).
164 | */
165 | public function testResolvedSetContainsNameOfClassThatIsReferencedByParentWithClassTableStrategy()
166 | {
167 | $resolver = new EntityDependencyResolver(array(
168 | ClassTableChildWithParentReferenceEntity::class
169 | ));
170 |
171 | $this->assertContainsEntity(
172 | ReferencedEntity::class,
173 | $resolver
174 | );
175 | }
176 |
177 | /**
178 | * Ensures that an entity, that is referenced by a mapped super class, is listed in the resolved set.
179 | */
180 | public function testResolvedSetContainsNameOfEntityThatIsReferencedByMappedSuperClass()
181 | {
182 | $resolver = new EntityDependencyResolver(array(
183 | MappedSuperClassChild::class
184 | ));
185 |
186 | $this->assertContainsEntity(
187 | ReferencedEntity::class,
188 | $resolver
189 | );
190 | }
191 |
192 | /**
193 | * Ensures that the resolved set contains the entities that are explicitly mentioned in
194 | * a discriminator map.
195 | *
196 | * Doctrine uses the information from the discriminator map to generate its queries.
197 | * Therefore, the tables on the mentioned entities must be generated in the tests.
198 | */
199 | public function testResolvedSetContainsNamesOfEntitiesThatAreMentionedInDiscriminatorMap()
200 | {
201 | $resolver = new EntityDependencyResolver(array(
202 | DiscriminatorMapEntity::class
203 | ));
204 |
205 | $this->assertContainsEntity(
206 | DiscriminatorMapChildEntity::class,
207 | $resolver
208 | );
209 | }
210 |
211 | /**
212 | * Interfaces can be used as association targets, but this simple resolver cannot handle them.
213 | * Nevertheless, the resolver should not fail and the interfaces should not show up in the dependency list.
214 | */
215 | public function testResolvedSetDoesNotContainInterfaces()
216 | {
217 | $resolver = new EntityDependencyResolver([
218 | EntityWithAssociationAgainstInterface::class
219 | ]);
220 |
221 | $this->assertNotContains(EntityInterface::class, $this->getResolvedSet($resolver));
222 | }
223 |
224 | /**
225 | * Returns the resolved set of entity classes as array.
226 | *
227 | * @param EntityDependencyResolver $resolver
228 | * @return string[]
229 | */
230 | protected function getResolvedSet(EntityDependencyResolver $resolver)
231 | {
232 | $this->assertInstanceOf(\Traversable::class, $resolver);
233 | $entities = iterator_to_array($resolver);
234 | $this->assertContainsOnly('string', $entities);
235 | return $entities;
236 | }
237 |
238 | /**
239 | * Asserts that the resolved entity list contains the given entity.
240 | *
241 | * @param string $entity Name of the entity class.
242 | * @param EntityDependencyResolver|mixed $resolver
243 | */
244 | protected function assertContainsEntity($entity, $resolver)
245 | {
246 | $normalizedEntity = ltrim($entity, '\\');
247 | $this->assertContains(
248 | $normalizedEntity,
249 | $this->getResolvedSet($resolver)
250 | );
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/src/ORMTestInfrastructure/ORMInfrastructure.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\ORMTestInfrastructure;
11 |
12 | use Doctrine\Common\EventManager;
13 | use Doctrine\DBAL\DriverManager;
14 | use Doctrine\DBAL\Logging\Middleware as LoggingMiddleware;
15 | use Doctrine\Persistence\Mapping\Driver\MappingDriver;
16 | use Doctrine\Persistence\ObjectRepository;
17 | use Doctrine\ORM\EntityManager;
18 | use Doctrine\ORM\Events;
19 | use Doctrine\ORM\Mapping\ClassMetadataFactory;
20 | use Doctrine\ORM\Mapping\DefaultNamingStrategy;
21 | use Doctrine\ORM\Mapping\NamingStrategy;
22 | use Doctrine\ORM\Tools\ResolveTargetEntityListener;
23 | use Doctrine\ORM\Tools\SchemaTool;
24 | use Doctrine\Common\EventSubscriber;
25 | use Webfactory\Doctrine\Config\ConnectionConfiguration;
26 | use Webfactory\Doctrine\Config\ExistingConnectionConfiguration;
27 |
28 | /**
29 | * Helper class that creates the database infrastructure for a defined set of entity classes.
30 | *
31 | * The required database is created in memory (via SQLite). This provides full isolation
32 | * and allows testing repositories and entities against a real database.
33 | *
34 | * # Example #
35 | *
36 | * ## Setup ##
37 | *
38 | * Create the infrastructure for a set of entities:
39 | *
40 | * $infrastructure = ORMInfrastructure::createOnlyFor(array(
41 | * 'My\Entity\ClassName'
42 | * ));
43 | *
44 | * Use the infrastructure to retrieve the entity manager:
45 | *
46 | * $entityManager = $infrastructure->getEntityManager();
47 | *
48 | * The entity manager can be used as usual. It operates on an in-memory database that contains
49 | * the schema for all entities that have been mentioned in the infrastructure constructor.
50 | *
51 | * ### Advanced Setup ###
52 | *
53 | * Use the ``createWithDependenciesFor()`` factory method to create an infrastructure for
54 | * the given entity, including all entities that are associated with it:
55 | *
56 | * $infrastructure = ORMInfrastructure::createWithDependenciesFor(
57 | * 'My\Entity\ClassName'
58 | * );
59 | *
60 | * This is convenient as it avoids touching tests when associations are added, but it
61 | * might also hide the existence of entity dependencies that you are not really aware
62 | * of.
63 | *
64 | * ## Import Test Data ##
65 | *
66 | * Additionally, the infrastructure provides means to import entities:
67 | *
68 | * $myEntity = new \My\Entity\ClassName();
69 | * $infrastructure->import($myEntity);
70 | *
71 | * The import ensures that the imported entities are loaded from the database when requested via repository. This
72 | * circumvents Doctrine's caching via identity map and thereby leads to a more realistic test environment.
73 | */
74 | class ORMInfrastructure
75 | {
76 | /**
77 | * List of entity classes that are managed by this infrastructure.
78 | *
79 | * @var string[]
80 | */
81 | protected $entityClasses;
82 |
83 | /**
84 | * The entity manager that is used to perform entity operations.
85 | *
86 | * Contains null if the entity manager has not been created yet.
87 | *
88 | * @var \Doctrine\ORM\EntityManager|null
89 | */
90 | protected $entityManager = null;
91 |
92 | /**
93 | * The query logger that is used.
94 | *
95 | * @var DebugStack
96 | */
97 | protected $queryLogger = null;
98 |
99 | /**
100 | * The naming strategy that is used.
101 | *
102 | * @var NamingStrategy
103 | */
104 | protected $namingStrategy = null;
105 |
106 | private readonly ?MappingDriver $mappingDriver;
107 |
108 | /**
109 | * Listener that is used to resolve entity mappings.
110 | *
111 | * Null if the listener is not registered yet.
112 | *
113 | * @var ResolveTargetEntityListener|null
114 | */
115 | private $resolveTargetListener;
116 |
117 | /**
118 | * The configuration that is used to connect to the test database.
119 | *
120 | * @var ConnectionConfiguration
121 | */
122 | private $connectionConfiguration = null;
123 |
124 | /**
125 | * @var bool
126 | */
127 | private $createSchema = true;
128 |
129 | /**
130 | * @var EventSubscriber[]
131 | */
132 | private $eventSubscribers;
133 |
134 | /**
135 | * Creates an infrastructure for the given entity or entities, including all
136 | * referenced entities.
137 | *
138 | * @param string[]|string $entityClassOrClasses
139 | * @param ConnectionConfiguration|null $connectionConfiguration Optional, specific database connection information.
140 | * @return ORMInfrastructure
141 | */
142 | public static function createWithDependenciesFor($entityClassOrClasses, ?ConnectionConfiguration $connectionConfiguration = null, ?MappingDriver $mappingDriver = null) {
143 | $entityClasses = static::normalizeEntityList($entityClassOrClasses);
144 | return new static(new EntityDependencyResolver($entityClasses, $mappingDriver), $connectionConfiguration, $mappingDriver);
145 | }
146 |
147 | /**
148 | * Creates an infrastructure for the given entity or entities.
149 | *
150 | * The infrastructure that is required for entities that are associated with the given
151 | * entities is *not* created automatically.
152 | *
153 | * @param string[]|string $entityClassOrClasses
154 | * @param ConnectionConfiguration|null $connectionConfiguration Optional, specific database connection information.
155 | * @return ORMInfrastructure
156 | */
157 | public static function createOnlyFor($entityClassOrClasses, ?ConnectionConfiguration $connectionConfiguration = null, ?MappingDriver $mappingDriver = null)
158 | {
159 | return new static(static::normalizeEntityList($entityClassOrClasses), $connectionConfiguration, $mappingDriver);
160 | }
161 |
162 | /**
163 | * Accepts a single entity class or a list of entity classes and always returns a
164 | * list of entity classes.
165 | *
166 | * @param string[]|string $entityClassOrClasses
167 | * @return string[]
168 | */
169 | protected static function normalizeEntityList($entityClassOrClasses)
170 | {
171 | $entityClasses = (is_string($entityClassOrClasses)) ? array($entityClassOrClasses) : $entityClassOrClasses;
172 | static::assertClassNames($entityClasses);
173 | return $entityClasses;
174 | }
175 |
176 | /**
177 | * Creates an entity helper that provides a database infrastructure
178 | * for the provided entities.
179 | *
180 | * Foreach entity the fully qualified class name must be provided.
181 | *
182 | * @param string[]|\Traversable $entityClasses
183 | * @param ConnectionConfiguration|null $connectionConfiguration Optional, specific database connection information.
184 | */
185 | private function __construct($entityClasses, ?ConnectionConfiguration $connectionConfiguration = null, ?MappingDriver $mappingDriver = null)
186 | {
187 | if ($entityClasses instanceof \Traversable) {
188 | $entityClasses = iterator_to_array($entityClasses);
189 | }
190 | if ($connectionConfiguration === null) {
191 | $connectionConfiguration = new ConnectionConfiguration([
192 | 'driver' => 'pdo_sqlite',
193 | 'user' => 'root',
194 | 'password' => '',
195 | 'memory' => true,
196 | ]);
197 | }
198 | $this->entityClasses = $entityClasses;
199 | $this->connectionConfiguration = $connectionConfiguration;
200 | $this->queryLogger = new QueryLogger();
201 | $this->namingStrategy = new DefaultNamingStrategy();
202 | $this->mappingDriver = $mappingDriver;
203 | $this->resolveTargetListener = new ResolveTargetEntityListener();
204 |
205 | $this->eventSubscribers = [$this->resolveTargetListener];
206 | }
207 |
208 | public function addEventSubscriber(EventSubscriber $subscriber): void
209 | {
210 | $this->eventSubscribers[] = $subscriber;
211 | }
212 |
213 | public function disableSchemaCreation()
214 | {
215 | $this->createSchema = false;
216 | }
217 |
218 | /**
219 | * @param NamingStrategy $namingStrategy
220 | */
221 | public function setNamingStrategy(NamingStrategy $namingStrategy): void
222 | {
223 | $this->namingStrategy = $namingStrategy;
224 | }
225 |
226 | /**
227 | * Returns the repository for the provided entity.
228 | *
229 | * @param string|object $classNameOrEntity Class name of an entity or entity instance.
230 | * @return ObjectRepository
231 | */
232 | public function getRepository($classNameOrEntity)
233 | {
234 | $className = is_object($classNameOrEntity) ? get_class($classNameOrEntity) : $classNameOrEntity;
235 | return $this->getEntityManager()->getRepository($className);
236 | }
237 |
238 | /**
239 | * Returns the queries that have been executed so far.
240 | *
241 | * @return Query[]
242 | */
243 | public function getQueries()
244 | {
245 | return $this->queryLogger->getQueries();
246 | }
247 |
248 | /**
249 | * Imports entities from the provided data source.
250 | *
251 | * The supported data sources are documented at \Webfactory\Doctrine\ORMTestInfrastructure\Importer::import().
252 | *
253 | * @param mixed $dataSource Callback, single entity, array of entities or file path.
254 | * @see \Webfactory\Doctrine\ORMTestInfrastructure\Importer::import()
255 | */
256 | public function import($dataSource)
257 | {
258 | $loggerWasEnabled = $this->queryLogger->enabled;
259 | $this->queryLogger->enabled = false;
260 | $importer = new Importer($this->copyEntityManager());
261 | $importer->import($dataSource);
262 | $this->queryLogger->enabled = $loggerWasEnabled;
263 | }
264 |
265 | /**
266 | * Returns the entity manager.
267 | *
268 | * @return \Doctrine\ORM\EntityManager
269 | */
270 | public function getEntityManager()
271 | {
272 | if ($this->entityManager === null) {
273 | $loggerWasEnabled = $this->queryLogger->enabled;
274 | $this->queryLogger->enabled = false;
275 | $this->entityManager = $this->createEntityManager();
276 | $this->setupEventSubscribers();
277 | if ($this->createSchema) {
278 | $this->createSchemaForSupportedEntities();
279 | }
280 | $this->queryLogger->enabled = $loggerWasEnabled;
281 | }
282 | return $this->entityManager;
283 | }
284 |
285 | /**
286 | * Returns the event manager that will be used by the entity manager.
287 | *
288 | * Can be used to register type mappings for interfaces.
289 | *
290 | * @return EventManager
291 | * @internal Do not rely on this method if you don't have to. Might be removed in future versions.
292 | */
293 | public function getEventManager()
294 | {
295 | return $this->getEntityManager()->getEventManager();
296 | }
297 |
298 | /**
299 | * Registers a type mapping.
300 | *
301 | * Might be required if you define an association mapping against an interface.
302 | *
303 | * @param string $originalEntity
304 | * @param string $targetEntity
305 | * @throws \LogicException If you call this method after using the infrastructure.
306 | * @internal Might be replaced in the future by a more advanced config system.
307 | * Do not rely on this feature if you don't have to.
308 | * @see http://symfony.com/doc/current/doctrine/resolve_target_entity.html#set-up
309 | */
310 | public function registerEntityMapping($originalEntity, $targetEntity)
311 | {
312 | if ($this->entityManager !== null) {
313 | $message = 'Call %s() before using the entity manager or importing data. '
314 | . 'Otherwise your entity mapping might not take effect.';
315 | throw new \LogicException(sprintf($message, __FUNCTION__));
316 | }
317 | $this->resolveTargetListener->addResolveTargetEntity($originalEntity, $targetEntity, array());
318 | }
319 |
320 | /**
321 | * Creates a new entity manager.
322 | *
323 | * @return \Doctrine\ORM\EntityManager
324 | */
325 | protected function createEntityManager()
326 | {
327 | $configFactory = new ConfigurationFactory($this->mappingDriver);
328 | $config = $configFactory->createFor($this->entityClasses);
329 | $middlewares = $config->getMiddlewares();
330 | $middlewares[] = new LoggingMiddleware($this->queryLogger);
331 | $config->setMiddlewares($middlewares);
332 | $config->setNamingStrategy($this->namingStrategy);
333 |
334 | if ($this->connectionConfiguration instanceof ExistingConnectionConfiguration) {
335 | $connection = $this->connectionConfiguration->getConnection();
336 | } else {
337 | $connection = DriverManager::getConnection($this->connectionConfiguration->getConnectionParameters(), $config);
338 | }
339 |
340 | return new EntityManager($connection, $config);
341 | }
342 |
343 | /**
344 | * Creates the schema for the managed entities.
345 | */
346 | protected function createSchemaForSupportedEntities()
347 | {
348 | $metadata = $this->getMetadataForSupportedEntities();
349 | $schemaTool = new SchemaTool($this->entityManager);
350 | $schemaTool->createSchema($metadata);
351 | }
352 |
353 | /**
354 | * Returns the metadata for each managed entity.
355 | *
356 | * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata[]
357 | */
358 | public function getMetadataForSupportedEntities()
359 | {
360 | $metadataFactory = $this->getEntityManager()->getMetadataFactory();
361 | $metadata = array();
362 | foreach ($this->entityClasses as $class) {
363 | $metadata[] = $metadataFactory->getMetadataFor($class);
364 | }
365 | return $metadata;
366 | }
367 |
368 | /**
369 | * Creates a copy of the current entity manager.
370 | *
371 | * @return EntityManager
372 | */
373 | private function copyEntityManager()
374 | {
375 | $entityManager = $this->getEntityManager();
376 |
377 | return new EntityManager(
378 | $entityManager->getConnection(),
379 | $entityManager->getConfiguration(),
380 | $this->getEventManager()
381 | );
382 | }
383 |
384 | private function setupEventSubscribers()
385 | {
386 | $eventManager = $this->getEventManager();
387 |
388 | foreach ($this->eventSubscribers as $subscriber) {
389 | $eventManager->addEventSubscriber($subscriber);
390 | }
391 | }
392 |
393 | /**
394 | * Checks if all entries in the given list are names of existing classes.
395 | *
396 | * @param string[] $classes
397 | * @throws \InvalidArgumentException If an entry is not a valid class name.
398 | */
399 | private static function assertClassNames(array $classes)
400 | {
401 | foreach ($classes as $class) {
402 | if (class_exists($class, true)) {
403 | continue;
404 | }
405 | $message = sprintf('"%s" is no existing class. Did you configure your autoloader correctly?', $class);
406 | throw new \InvalidArgumentException($message);
407 | }
408 | }
409 | }
410 |
--------------------------------------------------------------------------------
/tests/ORMTestInfrastructure/ORMInfrastructureTest.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | namespace Webfactory\Doctrine\Tests\ORMTestInfrastructure;
11 |
12 | use Doctrine\Common\EventManager;
13 | use Doctrine\DBAL\Schema\Schema;
14 | use Doctrine\ORM\EntityManager;
15 | use Doctrine\ORM\Mapping\ClassMetadata;
16 | use Doctrine\ORM\Mapping\Driver\AttributeDriver;
17 | use Doctrine\ORM\Tools\SchemaTool;
18 | use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
19 | use PHPUnit\Framework\TestCase;
20 | use Webfactory\Doctrine\Config\ConnectionConfiguration;
21 | use Webfactory\Doctrine\ORMTestInfrastructure\ORMInfrastructure;
22 | use Webfactory\Doctrine\ORMTestInfrastructure\Query;
23 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace2\TestEntity as TestEntity_Namespace2;
24 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace2\TestEntityWithDependency as TestEntityWithDependency_Attributes;
25 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Cascade\CascadePersistedEntity;
26 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\Cascade\CascadePersistingEntity;
27 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ChainReferenceEntity;
28 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\DependencyResolverFixtures;
29 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\InterfaceAssociation\EntityImplementation;
30 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\InterfaceAssociation\EntityInterface;
31 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\InterfaceAssociation\EntityWithAssociationAgainstInterface;
32 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ReferenceCycleEntity;
33 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\ReferencedEntity;
34 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\TestEntity;
35 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\TestEntityRepository;
36 | use Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1\TestEntityWithDependency;
37 |
38 | /**
39 | * Tests the infrastructure.
40 | */
41 | class ORMInfrastructureTest extends TestCase
42 | {
43 | /**
44 | * System under test.
45 | *
46 | * @var \Webfactory\Doctrine\ORMTestInfrastructure\ORMInfrastructure
47 | */
48 | protected $infrastructure = null;
49 |
50 | /**
51 | * Initializes the test environment.
52 | */
53 | protected function setUp(): void
54 | {
55 | parent::setUp();
56 | $this->infrastructure = ORMInfrastructure::createOnlyFor([
57 | TestEntity::class
58 | ]);
59 | }
60 |
61 | /**
62 | * Checks if getEntityManager() returns the Doctrine entity manager,
63 | */
64 | public function testGetEntityManagerReturnsDoctrineEntityManager()
65 | {
66 | $entityManager = $this->infrastructure->getEntityManager();
67 |
68 | $this->assertInstanceOf(EntityManager::class, $entityManager);
69 | }
70 |
71 | /**
72 | * Ensures that getRepository() returns the Doctrine repository that belongs
73 | * to the given entity class.
74 | */
75 | public function testGetRepositoryReturnsRepositoryThatBelongsToEntityClass()
76 | {
77 | $repository = $this->infrastructure->getRepository(
78 | TestEntity::class
79 | );
80 |
81 | $this->assertInstanceOf(
82 | TestEntityRepository::class,
83 | $repository
84 | );
85 | }
86 |
87 | /**
88 | * Ensures that getRepository() returns the Doctrine repository that belongs
89 | * to the given entity object.
90 | */
91 | public function testGetRepositoryReturnsRepositoryThatBelongsToEntityObject()
92 | {
93 | $entity = new TestEntity();
94 | $repository = $this->infrastructure->getRepository($entity);
95 |
96 | $this->assertInstanceOf(
97 | TestEntityRepository::class,
98 | $repository
99 | );
100 | }
101 |
102 | /**
103 | * Ensure that the infrastructure fails fast if obviously invalid data is passed.
104 | */
105 | public function testInfrastructureRejectsNonClassNames()
106 | {
107 | $this->expectException(\InvalidArgumentException::class);
108 | ORMInfrastructure::createOnlyFor(array('NotAClass'));
109 | }
110 |
111 | /**
112 | * Checks if import() adds entities to the database.
113 | *
114 | * There are different options to import entities, but these are handled in detail
115 | * in the importer tests.
116 | *
117 | * @see \Webfactory\Doctrine\ORMTestInfrastructure\ImporterTest
118 | */
119 | public function testImportAddsEntities()
120 | {
121 | $entity = new TestEntity();
122 | $repository = $this->infrastructure->getRepository($entity);
123 |
124 | $entities = $repository->findAll();
125 | $this->assertCount(0, $entities);
126 |
127 | $this->infrastructure->import($entity);
128 |
129 | $entities = $repository->findAll();
130 | $this->assertCount(1, $entities);
131 | }
132 |
133 | public function testImportEntityWithAttributeMapping()
134 | {
135 | if (PHP_VERSION_ID < 80000) {
136 | self::markTestSkipped('This test requires PHP 8.0 or greater');
137 | }
138 |
139 | $this->infrastructure = ORMInfrastructure::createOnlyFor([TestEntity_Namespace2::class], null, new AttributeDriver([__DIR__.'/Fixtures/EntityNamespace2']));
140 |
141 | $entity = new TestEntity_Namespace2();
142 | $repository = $this->infrastructure->getRepository($entity);
143 |
144 | $entities = $repository->findAll();
145 | $this->assertCount(0, $entities);
146 |
147 | $this->infrastructure->import($entity);
148 |
149 | $entities = $repository->findAll();
150 | $this->assertCount(1, $entities);
151 | }
152 |
153 | /**
154 | * Checks if an imported entity receives a generated ID.
155 | */
156 | public function testEntityIdIsAvailableAfterImport()
157 | {
158 | $entity = new TestEntity();
159 |
160 | $this->infrastructure->import($entity);
161 |
162 | $this->assertNotNull($entity->id);
163 | }
164 |
165 | /**
166 | * Ensures that imported entities are really loaded from the database and
167 | * not provided from identity map.
168 | */
169 | public function testImportedEntitiesAreReloadedFromDatabase()
170 | {
171 | $entity = new TestEntity();
172 | $repository = $this->infrastructure->getRepository($entity);
173 |
174 | $this->infrastructure->import($entity);
175 |
176 | $loadedEntity = $repository->find($entity->id);
177 | $this->assertInstanceOf(
178 | TestEntity::class,
179 | $loadedEntity
180 | );
181 | $this->assertNotSame($entity, $loadedEntity);
182 | }
183 |
184 | /**
185 | * Ensures that different infrastructure instances provide database isolation.
186 | */
187 | public function testDifferentInfrastructureInstancesUseSeparatedDatabases()
188 | {
189 | $entity = new TestEntity();
190 | $anotherInfrastructure = ORMInfrastructure::createOnlyFor([
191 | TestEntity::class
192 | ]);
193 | $repository = $anotherInfrastructure->getRepository($entity);
194 |
195 | $this->infrastructure->import($entity);
196 |
197 | // Entity must not be visible in the scope of another infrastructure.
198 | $entities = $repository->findAll();
199 | $this->assertCount(0, $entities);
200 | }
201 |
202 | /**
203 | * Ensures that the query list that is provided by getQueries() is initially empty.
204 | */
205 | public function testGetQueriesReturnsInitiallyEmptyList()
206 | {
207 | $queries = $this->infrastructure->getQueries();
208 |
209 | $this->assertIsArray($queries);
210 | $this->assertCount(0, $queries);
211 | }
212 |
213 | /**
214 | * Ensures that getQueries() returns the logged SQL queries as objects.
215 | */
216 | public function testGetQueriesReturnsQueryObjects()
217 | {
218 | $entity = new TestEntity();
219 | $repository = $this->infrastructure->getRepository($entity);
220 | $repository->find(42);
221 |
222 | $queries = $this->infrastructure->getQueries();
223 |
224 | $this->assertIsArray($queries);
225 | $this->assertContainsOnly(Query::class, $queries);
226 | }
227 |
228 | /**
229 | * Checks if the queries that are executed with the entity manager are logged.
230 | */
231 | public function testInfrastructureLogsExecutedQueries()
232 | {
233 | $entity = new TestEntity();
234 | $repository = $this->infrastructure->getRepository($entity);
235 | $repository->find(42);
236 |
237 | $queries = $this->infrastructure->getQueries();
238 |
239 | $this->assertIsArray($queries);
240 | $this->assertCount(1, $queries);
241 | }
242 |
243 | /**
244 | * Ensures that the queries that are issued during data import are not logged.
245 | */
246 | public function testInfrastructureDoesNotLogImportQueries()
247 | {
248 | $entity = new TestEntity();
249 | $this->infrastructure->import($entity);
250 |
251 | $queries = $this->infrastructure->getQueries();
252 |
253 | $this->assertIsArray($queries);
254 | $this->assertCount(0, $queries);
255 | }
256 |
257 | /**
258 | * Ensures that the infrastructure logs queries, which are executed after an import.
259 | */
260 | public function testInfrastructureLogsQueriesThatAreExecutedAfterImport()
261 | {
262 | $entity = new TestEntity();
263 | $this->infrastructure->import($entity);
264 | $repository = $this->infrastructure->getRepository($entity);
265 | $repository->find(42);
266 |
267 | $queries = $this->infrastructure->getQueries();
268 |
269 | $this->assertIsArray($queries);
270 | $this->assertCount(1, $queries);
271 | }
272 |
273 | /**
274 | * Ensures that createWithDependenciesFor() returns an infrastructure object if a set of
275 | * entities classes is provided.
276 | */
277 | public function testCreateWithDependenciesForCreatesInfrastructureForSetOfEntities()
278 | {
279 | $infrastructure = ORMInfrastructure::createWithDependenciesFor(array(
280 | TestEntity::class,
281 | ReferencedEntity::class
282 | ));
283 |
284 | $this->assertInstanceOf(ORMInfrastructure::class, $infrastructure);
285 | }
286 |
287 | /**
288 | * Ensures that createWithDependenciesFor() returns an infrastructure object if a single
289 | * entity class is provided.
290 | */
291 | public function testCreateWithDependenciesForCreatesInfrastructureForSingleEntity()
292 | {
293 | $infrastructure = ORMInfrastructure::createWithDependenciesFor(
294 | TestEntity::class
295 | );
296 |
297 | $this->assertInstanceOf(ORMInfrastructure::class, $infrastructure);
298 | }
299 |
300 | /**
301 | * Ensures that createOnlyFor() returns an infrastructure object if a set of
302 | * entities classes is provided.
303 | */
304 | public function testCreateOnlyForCreatesInfrastructureForSetOfEntities()
305 | {
306 | $infrastructure = ORMInfrastructure::createOnlyFor(array(
307 | TestEntity::class,
308 | ReferencedEntity::class
309 | ));
310 |
311 | $this->assertInstanceOf(ORMInfrastructure::class, $infrastructure);
312 | }
313 |
314 | /**
315 | * Ensures that createOnlyFor() returns an infrastructure object if a single
316 | * entity class is provided.
317 | */
318 | public function testCreateOnlyForCreatesInfrastructureForSingleEntity()
319 | {
320 | $infrastructure = ORMInfrastructure::createOnlyFor(
321 | TestEntity::class
322 | );
323 |
324 | $this->assertInstanceOf(ORMInfrastructure::class, $infrastructure);
325 | }
326 |
327 | /**
328 | * Ensures that referenced sub-entities are automatically prepared if the infrastructure is
329 | * requested to handle such cases.
330 | */
331 | public function testInfrastructureAutomaticallyPerformsDependencySetupIfRequested()
332 | {
333 | $infrastructure = ORMInfrastructure::createWithDependenciesFor(array(
334 | TestEntityWithDependency::class
335 | ));
336 |
337 | $entityWithDependency = new TestEntityWithDependency();
338 |
339 | // Saving without prepared sub-entity would fail.
340 | $infrastructure->getEntityManager()->persist($entityWithDependency);
341 | $this->assertNull(
342 | $infrastructure->getEntityManager()->flush()
343 | );
344 | }
345 |
346 | /**
347 | * Ensures that referenced sub-entities are automatically prepared if the infrastructure is
348 | * requested to handle such cases.
349 | */
350 | public function testInfrastructureAutomaticallyPerformsDependencySetupAcrossMappingDrivers()
351 | {
352 | $mappingDriver = new MappingDriverChain();
353 | $mappingDriver->addDriver(new AttributeDriver([__DIR__.'/Fixtures/EntityNamespace1']), 'Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace1');
354 | $mappingDriver->addDriver(new AttributeDriver([__DIR__.'/Fixtures/EntityNamespace2']), 'Webfactory\Doctrine\Tests\ORMTestInfrastructure\Fixtures\EntityNamespace2');
355 |
356 | $this->infrastructure = ORMInfrastructure::createWithDependenciesFor([TestEntityWithDependency_Attributes::class], null, $mappingDriver);
357 |
358 | $entityWithDependency = new TestEntityWithDependency_Attributes();
359 |
360 | self::expectNotToPerformAssertions();
361 |
362 | $this->infrastructure->getEntityManager()->persist($entityWithDependency);
363 | $this->infrastructure->getEntityManager()->flush();
364 | }
365 |
366 | /**
367 | * Checks if the automatic dependency setup can cope with reference cycles,
368 | * for example if an entity references itself.
369 | */
370 | public function testAutomaticDependencyDetectionCanHandleCycles()
371 | {
372 | $infrastructure = ORMInfrastructure::createWithDependenciesFor(array(
373 | ReferenceCycleEntity::class
374 | ));
375 |
376 | $entityWithCycle = new ReferenceCycleEntity();
377 |
378 | // Saving will most probably work as no additional table is needed, but the reference
379 | // detection, which is performed before, might lead to an endless loop.
380 | $infrastructure->getEntityManager()->persist($entityWithCycle);
381 | $this->assertNull(
382 | $infrastructure->getEntityManager()->flush()
383 | );
384 | }
385 |
386 | /**
387 | * Checks if the automatic dependency setup can cope with chained references.
388 | *
389 | * Example:
390 | *
391 | * A -> B -> C
392 | *
393 | * A references B, B references C. A is not directly related to C.
394 | */
395 | public function testAutomaticDependencyDetectionCanHandleChainedRelations()
396 | {
397 | $infrastructure = ORMInfrastructure::createWithDependenciesFor(array(
398 | ChainReferenceEntity::class
399 | ));
400 |
401 | $entityWithReferenceChain = new ChainReferenceEntity();
402 |
403 | // All tables must be created properly, otherwise it is not possible to store the entity.
404 | $infrastructure->getEntityManager()->persist($entityWithReferenceChain);
405 | $this->assertNull(
406 | $infrastructure->getEntityManager()->flush()
407 | );
408 | }
409 |
410 | /**
411 | * Ensures that it is not possible to retrieve the class names of entities,
412 | * which are not simulated by the infrastructure.
413 | *
414 | * If not handled properly, the metadata provides access to several entity classes.
415 | */
416 | public function testNotSimulatedEntitiesAreNotExposed()
417 | {
418 | $infrastructure = ORMInfrastructure::createOnlyFor(array(
419 | TestEntity::class
420 | ));
421 |
422 | $metadata = $infrastructure->getEntityManager()->getMetadataFactory()->getAllMetadata();
423 | $entities = array_map(function (ClassMetadata $info) {
424 | return ltrim($info->name, '\\');
425 | }, $metadata);
426 | $this->assertEquals(
427 | array(TestEntity::class),
428 | $entities
429 | );
430 | }
431 |
432 | public function testGetEventManagerReturnsEventManager()
433 | {
434 | $this->assertInstanceOf(EventManager::class, $this->infrastructure->getEventManager());
435 | }
436 |
437 | public function testGetEventManagerReturnsSameEventManagerThatIsUsedByEntityManager()
438 | {
439 | $this->assertSame(
440 | $this->infrastructure->getEventManager(),
441 | $this->infrastructure->getEntityManager()->getEventManager()
442 | );
443 | }
444 |
445 | public function testCanHandleInterfaceAssociationsIfMappingIsProvided()
446 | {
447 | $infrastructure = ORMInfrastructure::createWithDependenciesFor(EntityWithAssociationAgainstInterface::class);
448 |
449 | $infrastructure->registerEntityMapping(EntityInterface::class, EntityImplementation::class);
450 |
451 | $this->assertInstanceOf(
452 | EntityManager::class,
453 | $infrastructure->getEntityManager()
454 | );
455 | }
456 |
457 | public function testCannotRegisterEntityMappingAfterEntityManagerCreation()
458 | {
459 | $this->infrastructure->getEntityManager();
460 |
461 | $this->expectException(\LogicException::class);
462 | $this->assertNull(
463 | $this->infrastructure->registerEntityMapping(EntityInterface::class, EntityImplementation::class)
464 | );
465 | }
466 |
467 | /**
468 | * Checks if it is possible to pass a more specific connection configuration.
469 | */
470 | public function testUsesMoreSpecificConnectionConfiguration()
471 | {
472 | $this->infrastructure = ORMInfrastructure::createOnlyFor([
473 | TestEntity::class
474 | ], new ConnectionConfiguration([
475 | 'invalid' => 'configuration'
476 | ]));
477 |
478 | // The passed configuration is simply invalid, therefore, we expect an exception.
479 | $this->expectException('Exception');
480 | $this->infrastructure->getEntityManager();
481 | }
482 |
483 | /**
484 | * @see https://github.com/webfactory/doctrine-orm-test-infrastructure/issues/23
485 | */
486 | public function testWorksWithCascadePersist()
487 | {
488 | $infrastructure = ORMInfrastructure::createWithDependenciesFor(CascadePersistingEntity::class);
489 | $cascadingPersistingEntity = new CascadePersistingEntity();
490 | $cascadingPersistingEntity->add(new CascadePersistedEntity());
491 | $infrastructure->import($cascadingPersistingEntity);
492 |
493 | // If this call fails, then there are leftovers in the identity map.
494 | $this->assertNull(
495 | $infrastructure->getEntityManager()->flush()
496 | );
497 | }
498 |
499 | /**
500 | * @dataProvider resolverFixtures
501 | */
502 | public function testSchemaResults(array $classes, callable $validator): void
503 | {
504 | $infrastructure = ORMInfrastructure::createWithDependenciesFor($classes);
505 | $entityManager = $infrastructure->getEntityManager();
506 | $schemaTool = new SchemaTool($entityManager);
507 |
508 | $validator($schemaTool->getSchemaFromMetadata($infrastructure->getMetadataForSupportedEntities()));
509 | }
510 |
511 | public static function resolverFixtures()
512 | {
513 | yield 'single entity' => [
514 | [DependencyResolverFixtures\SingleEntity\Entity::class],
515 | function (Schema $schema) {
516 | self::assertCount(1, $schema->getTableNames());
517 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldA'));
518 | },
519 | ];
520 |
521 | yield 'simple entity hierarchy' => [
522 | [DependencyResolverFixtures\TwoEntitiesInheritance\Entity::class],
523 | function (Schema $schema) {
524 | self::assertCount(1, $schema->getTableNames());
525 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldA'));
526 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldB'));
527 | },
528 | ];
529 |
530 | yield 'entity with mapped superclass as base class' => [
531 | [DependencyResolverFixtures\MappedSuperclassInheritance\Entity::class],
532 | function (Schema $schema) {
533 | self::assertCount(1, $schema->getTableNames());
534 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldA'));
535 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldB'));
536 | },
537 | ];
538 |
539 | yield 'fields from transient base class are present, but class is otherwise ignored' => [
540 | [DependencyResolverFixtures\TransientBaseClass\Entity::class],
541 | function (Schema $schema) {
542 | self::assertCount(1, $schema->getTableNames());
543 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldA'));
544 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldB'));
545 | },
546 | ];
547 |
548 | yield 'with single table inheritance, the table for the base class is present with all fields' => [
549 | [DependencyResolverFixtures\SingleTableInheritance\Entity::class],
550 | function (Schema $schema) {
551 | self::assertCount(1, $schema->getTableNames());
552 | self::assertTrue($schema->getTable('BaseEntity')->hasColumn('fieldA'));
553 | self::assertTrue($schema->getTable('BaseEntity')->hasColumn('fieldB'));
554 | },
555 | ];
556 |
557 | yield 'with joined table inheritance, tables for the base and subclass are present with all fields' => [
558 | [DependencyResolverFixtures\JoinedTableInheritance\Entity::class],
559 | function (Schema $schema) {
560 | self::assertCount(2, $schema->getTableNames());
561 |
562 | self::assertCount(3, $schema->getTable('BaseEntity')->getColumns()); // id, class, fieldA
563 | self::assertTrue($schema->getTable('BaseEntity')->hasColumn('fieldA'));
564 |
565 | self::assertCount(2, $schema->getTable('Entity')->getColumns()); // id-baseref, fieldB
566 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldB'));
567 | },
568 | ];
569 |
570 | yield 'with joined table inheritance, three tables are present along the class hierarchy' => [
571 | [DependencyResolverFixtures\JoinedTableInheritanceWithTwoLevels\Entity::class],
572 | function (Schema $schema) {
573 | self::assertCount(3, $schema->getTableNames());
574 |
575 | self::assertCount(3, $schema->getTable('BaseEntity')->getColumns()); // id, class, fieldA
576 | self::assertTrue($schema->getTable('BaseEntity')->hasColumn('fieldA'));
577 |
578 | self::assertCount(2, $schema->getTable('IntermediateEntity')->getColumns()); // id-baseref, fieldB
579 | self::assertTrue($schema->getTable('IntermediateEntity')->hasColumn('fieldB'));
580 |
581 | self::assertCount(2, $schema->getTable('Entity')->getColumns()); // id-baseref, fieldC
582 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldC'));
583 | },
584 | ];
585 |
586 | yield 'with joined table inheritance, all tables for the complete inheritance tree are present' => [
587 | [DependencyResolverFixtures\JoinedTableInheritanceWithTwoSubclasses\Entity::class],
588 | function (Schema $schema) {
589 | self::assertCount(3, $schema->getTableNames());
590 |
591 | self::assertCount(3, $schema->getTable('BaseEntity')->getColumns()); // id, class, fieldA
592 | self::assertTrue($schema->getTable('BaseEntity')->hasColumn('fieldA'));
593 |
594 | self::assertCount(2, $schema->getTable('SecondEntity')->getColumns()); // id-baseref, fieldB
595 | self::assertTrue($schema->getTable('SecondEntity')->hasColumn('fieldB'));
596 |
597 | self::assertCount(2, $schema->getTable('Entity')->getColumns()); // id-baseref, fieldC
598 | self::assertTrue($schema->getTable('Entity')->hasColumn('fieldC'));
599 | },
600 | ];
601 | }
602 | }
603 |
--------------------------------------------------------------------------------