├── .gitattributes
├── tests
└── Provider
│ └── Doctrine
│ ├── Fixtures
│ ├── TestProxy
│ │ └── .gitignore
│ ├── TestEntity
│ │ ├── NotAnEntity.php
│ │ ├── Person
│ │ │ └── User.php
│ │ ├── SpaceStation.php
│ │ ├── Name.php
│ │ ├── Commander.php
│ │ ├── Badge.php
│ │ ├── Person.php
│ │ └── SpaceShip.php
│ ├── TestAnotherEntity
│ │ └── Artist.php
│ ├── ReferenceTest.php
│ ├── TestCase.php
│ ├── ExtraConfigurationTest.php
│ ├── TransitiveReferencesTest.php
│ ├── TimeTest.php
│ ├── BidirectionalReferencesTest.php
│ ├── SequenceTest.php
│ ├── IncorrectUsageTest.php
│ ├── SingletonTest.php
│ ├── ReferencesTest.php
│ ├── PersistingTest.php
│ └── BasicUsageTest.php
│ ├── ORM
│ ├── TestEntity
│ │ └── User.php
│ ├── TestCase.php
│ ├── RepositoryTest.php
│ └── DateIntervalHelperTest.php
│ ├── FixtureFactoryTest.php
│ ├── EntityDefinitionUnavailableTest.php
│ ├── TestDb.php
│ └── Types
│ └── StatusArrayTest.php
├── .gitignore
├── .editorconfig
├── src
└── Provider
│ └── Doctrine
│ ├── Exception.php
│ ├── ORM
│ ├── Locking
│ │ ├── LockException.php
│ │ ├── VersionLockable.php
│ │ ├── TableLockMode.php
│ │ └── TableLock.php
│ ├── QueryBuilder.php
│ └── Repository.php
│ ├── EntityDefinitionUnavailable.php
│ ├── DBAL
│ └── Types
│ │ └── StatusArrayType.php
│ ├── DateIntervalHelper.php
│ ├── FieldDef.php
│ ├── EntityDef.php
│ └── FixtureFactory.php
├── phpunit.xml.dist
├── .php_cs.dist
├── composer.json
└── README.markdown
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.github/ export-ignore
2 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TestProxy/.gitignore:
--------------------------------------------------------------------------------
1 | *.php
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | .php_cs
3 | .php_cs.cache
4 | composer.lock
5 | phpunit.xml
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_size = 4
6 | indent_style = space
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 |
10 | [*.yml]
11 | indent_size = 2
12 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/Exception.php:
--------------------------------------------------------------------------------
1 | name = $name;
24 | }
25 |
26 | public function getId()
27 | {
28 | return $this->id;
29 | }
30 |
31 | public function getName()
32 | {
33 | return $this->name;
34 | }
35 |
36 | public function setName($name)
37 | {
38 | $this->name = $name;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/FixtureFactoryTest.php:
--------------------------------------------------------------------------------
1 | prophesize(EntityManager::class)->reveal();
20 |
21 | $fixtureFactory = new FixtureFactory($entityManager);
22 |
23 | $this->expectException(EntityDefinitionUnavailable::class);
24 |
25 | $fixtureFactory->get('foo');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 | ./tests/
18 |
19 |
20 |
21 |
22 |
23 | ./src/
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TestEntity/Name.php:
--------------------------------------------------------------------------------
1 | first;
37 | }
38 |
39 | public function last(): ?string
40 | {
41 | return $this->last;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/EntityDefinitionUnavailableTest.php:
--------------------------------------------------------------------------------
1 | getMessage());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TestEntity/Commander.php:
--------------------------------------------------------------------------------
1 | name = new Name();
35 | }
36 |
37 | public function id(): string
38 | {
39 | return $this->id;
40 | }
41 |
42 | public function name(): Name
43 | {
44 | return $this->name;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TestEntity/Badge.php:
--------------------------------------------------------------------------------
1 | label = $label;
28 | $this->owner = $owner;
29 | }
30 |
31 | public function getId()
32 | {
33 | return $this->id;
34 | }
35 |
36 | public function getLabel()
37 | {
38 | return $this->label;
39 | }
40 |
41 | public function getOwner()
42 | {
43 | return $this->owner;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TestEntity/Person.php:
--------------------------------------------------------------------------------
1 | name = $name;
28 | $this->spaceShip = $spaceShip;
29 | }
30 |
31 | public function getId()
32 | {
33 | return $this->id;
34 | }
35 |
36 | public function getName()
37 | {
38 | return $this->name;
39 | }
40 |
41 | public function getSpaceShip()
42 | {
43 | return $this->spaceShip;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/ORM/TestCase.php:
--------------------------------------------------------------------------------
1 |
13 | * @license http://www.opensource.org/licenses/BSD-3-Clause New BSD License
14 | */
15 | abstract class TestCase extends Framework\TestCase
16 | {
17 | /**
18 | * @var TestDb
19 | */
20 | protected $testDb;
21 |
22 | /**
23 | * @var \Doctrine\ORM\EntityManager
24 | */
25 | protected $em;
26 |
27 | protected function setUp()
28 | {
29 | parent::setUp();
30 |
31 | $here = dirname(__FILE__);
32 |
33 | $this->testDb = new TestDb(
34 | $here . '/TestEntity',
35 | $here . '/TestProxy',
36 | 'FactoryGirl\Tests\Provider\Doctrine\ORM\TestProxy'
37 | );
38 |
39 | $this->em = $this->testDb->createEntityManager();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.php_cs.dist:
--------------------------------------------------------------------------------
1 | in(__DIR__);
4 |
5 | return PhpCsFixer\Config::create()
6 | ->setRiskyAllowed(true)
7 | ->setRules([
8 | '@PSR2' => true,
9 | 'array_syntax' => [
10 | 'syntax' => 'short',
11 | ],
12 | 'no_extra_blank_lines' => [
13 | 'tokens' => [
14 | 'break',
15 | 'case',
16 | 'continue',
17 | 'curly_brace_block',
18 | 'default',
19 | 'extra',
20 | 'parenthesis_brace_block',
21 | 'return',
22 | 'square_brace_block',
23 | 'switch',
24 | 'throw',
25 | 'use',
26 | 'use_trait',
27 | ],
28 | ],
29 | 'no_whitespace_in_blank_line' => true,
30 | 'php_unit_set_up_tear_down_visibility' => true,
31 | 'visibility_required' => [
32 | 'elements' => [
33 | 'const',
34 | 'method',
35 | 'property',
36 | ],
37 | ],
38 | ])
39 | ->setFinder($finder);
40 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/ReferenceTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('SpaceShip');
13 | $this->factory->defineEntity('Person', [
14 | 'name' => 'Eve',
15 | 'spaceShip' => FieldDef::reference('SpaceShip')
16 | ]);
17 | }
18 |
19 | /**
20 | * @test
21 | */
22 | public function referencedObjectShouldBeCreatedAutomatically()
23 | {
24 | $ss1 = $this->factory->get('Person')->getSpaceShip();
25 | $ss2 = $this->factory->get('Person')->getSpaceShip();
26 |
27 | $this->assertNotNull($ss1);
28 | $this->assertNotNull($ss2);
29 | $this->assertNotSame($ss1, $ss2);
30 | }
31 |
32 | /**
33 | * @test
34 | */
35 | public function referencedObjectsShouldBeNullable()
36 | {
37 | $person = $this->factory->get('Person', ['spaceShip' => null]);
38 |
39 | $this->assertNull($person->getSpaceShip());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TestCase.php:
--------------------------------------------------------------------------------
1 | testDb = new TestDb(
37 | $here . '/TestEntity',
38 | $here . '/TestProxy',
39 | 'FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestProxy'
40 | );
41 |
42 | $this->em = $this->testDb->createEntityManager();
43 |
44 | $this->factory = new FixtureFactory($this->em);
45 | $this->factory->setEntityNamespace('FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestEntity');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/ORM/RepositoryTest.php:
--------------------------------------------------------------------------------
1 |
13 | * @license http://www.opensource.org/licenses/BSD-3-Clause New BSD License
14 | */
15 | class RepositoryTest extends TestCase
16 | {
17 | /**
18 | * @var Repository
19 | */
20 | private $repository;
21 |
22 | protected function setUp()
23 | {
24 | parent::setUp();
25 |
26 | $this->repository = new Repository(
27 | $this->em,
28 | $this->em->getClassMetadata('FactoryGirl\Tests\Provider\Doctrine\ORM\TestEntity\User')
29 | );
30 | }
31 |
32 | /**
33 | * @test
34 | */
35 | public function getsReference()
36 | {
37 | $user = new User();
38 | $this->em->persist($user);
39 | $this->em->flush();
40 |
41 | $this->assertInstanceOf(
42 | 'FactoryGirl\Tests\Provider\Doctrine\ORM\TestEntity\User',
43 | $this->repository->getReference($user->id)
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TestEntity/SpaceShip.php:
--------------------------------------------------------------------------------
1 | name = $name;
34 | $this->crew = new ArrayCollection();
35 | $this->constructorWasCalled = true;
36 | }
37 |
38 | public function getId()
39 | {
40 | return $this->id;
41 | }
42 |
43 | public function getName()
44 | {
45 | return $this->name;
46 | }
47 |
48 | public function setName($name)
49 | {
50 | $this->name = $name;
51 | }
52 |
53 | public function getCrew()
54 | {
55 | return $this->crew;
56 | }
57 |
58 | public function constructorWasCalled()
59 | {
60 | return $this->constructorWasCalled;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/ExtraConfigurationTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('SpaceShip', [
12 | 'name' => 'Foo'
13 | ], [
14 | 'afterCreate' => function (TestEntity\SpaceShip $ss, array $fieldValues) {
15 | $ss->setName($ss->getName() . '-' . $fieldValues['name']);
16 | }
17 | ]);
18 | $ss = $this->factory->get('SpaceShip');
19 |
20 | $this->assertSame("Foo-Foo", $ss->getName());
21 | }
22 |
23 | /**
24 | * @test
25 | */
26 | public function theAfterCreateCallbackCanBeUsedToCallTheConstructor()
27 | {
28 | $this->factory->defineEntity('SpaceShip', [
29 | 'name' => 'Foo'
30 | ], [
31 | 'afterCreate' => function (TestEntity\SpaceShip $ss, array $fieldValues) {
32 | $ss->__construct($fieldValues['name'] . 'Master');
33 | }
34 | ]);
35 | $ss = $this->factory->get('SpaceShip', ['name' => 'Xoo']);
36 |
37 | $this->assertTrue($ss->constructorWasCalled());
38 | $this->assertSame('XooMaster', $ss->getName());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TransitiveReferencesTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('Person', [
12 | 'spaceShip' => FieldDef::reference('SpaceShip'),
13 | ]);
14 | $this->factory->defineEntity('Badge', [
15 | 'owner' => FieldDef::reference('Person')
16 | ]);
17 | $this->factory->defineEntity('SpaceShip');
18 | }
19 |
20 | /**
21 | * @test
22 | */
23 | public function referencesGetInstantiatedTransitively()
24 | {
25 | $this->simpleSetup();
26 |
27 | $badge = $this->factory->get('Badge');
28 |
29 | $this->assertNotNull($badge->getOwner()->getSpaceShip());
30 | }
31 |
32 | /**
33 | * @test
34 | */
35 | public function transitiveReferencesWorkWithSingletons()
36 | {
37 | $this->simpleSetup();
38 |
39 | $this->factory->getAsSingleton('SpaceShip');
40 | $badge1 = $this->factory->get('Badge');
41 | $badge2 = $this->factory->get('Badge');
42 |
43 | $this->assertNotSame($badge1->getOwner(), $badge2->getOwner());
44 | $this->assertSame($badge1->getOwner()->getSpaceShip(), $badge2->getOwner()->getSpaceShip());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "breerly/factory-girl-php",
3 | "type": "library",
4 | "description": "Fixture replacement for focused and readable tests - A PHP port of Thoughtbot's Ruby Factory Girl",
5 | "keywords": [
6 | "factory girl",
7 | "fixture factory",
8 | "fixture replacement",
9 | "object mother",
10 | "testing",
11 | "fixture",
12 | "tdd"
13 | ],
14 | "license": "MIT",
15 | "authors": [
16 | {
17 | "name": "Grayson Koonce",
18 | "email": "breerly@gmail.com"
19 | }
20 | ],
21 | "config": {
22 | "preferred-install": "dist",
23 | "sort-packages": true
24 | },
25 | "require": {
26 | "php": "^7.1",
27 | "doctrine/annotations": "^1.7.0",
28 | "doctrine/common": "^2.2.1",
29 | "doctrine/dbal": "^2.2.1",
30 | "doctrine/orm": "^2.6.3"
31 | },
32 | "require-dev": {
33 | "friendsofphp/php-cs-fixer": "^2.14.0",
34 | "phpunit/phpunit": "^7.5.1"
35 | },
36 | "suggest": {
37 | "fzaninotto/faker": "For generating fake data in entity definitions"
38 | },
39 | "autoload": {
40 | "psr-4": {
41 | "FactoryGirl\\": "src/"
42 | }
43 | },
44 | "autoload-dev": {
45 | "psr-4": {
46 | "FactoryGirl\\Tests\\": "tests/"
47 | }
48 | },
49 | "scripts": {
50 | "cs": "php-cs-fixer fix --diff --verbose"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/TimeTest.php:
--------------------------------------------------------------------------------
1 | invert = 1;
19 | $time->add($interval);
20 | $this->assertSame(
21 | $time->getTimestamp(),
22 | FieldDef::past()->years(3)->months(1)->days(2)->get(),
23 | 'Error getting unix timestamp'
24 | );
25 | $this->assertSame(
26 | $time->format('d-m-y'),
27 | FieldDef::past()->years(3)->months(1)->days(2)->get(DateIntervalHelper::DATE_STRING),
28 | 'Error getting string'
29 | );
30 | }
31 |
32 | public function testGetTimeFuture()
33 | {
34 | $time = new \DateTime();
35 | $interval = new \DateInterval('P3Y1M2D');
36 | $time->add($interval);
37 | $this->assertSame(
38 | $time->getTimestamp(),
39 | FieldDef::future()->years(3)->months(1)->days(2)->get()
40 | );
41 | $this->assertSame(
42 | $time->format('d-m-y'),
43 | FieldDef::future()->years(3)->months(1)->days(2)->get(DateIntervalHelper::DATE_STRING)
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/BidirectionalReferencesTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('SpaceShip');
14 | $this->factory->defineEntity('Person', [
15 | 'spaceShip' => FieldDef::reference('SpaceShip')
16 | ]);
17 |
18 | $person = $this->factory->get('Person');
19 | $ship = $person->getSpaceShip();
20 |
21 | $this->assertContains($person, $ship->getCrew());
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function unidirectionalReferencesWorkAsUsual()
28 | {
29 | $this->factory->defineEntity('Badge', [
30 | 'owner' => FieldDef::reference('Person')
31 | ]);
32 | $this->factory->defineEntity('Person');
33 |
34 | $this->assertInstanceOf(TestEntity\Person::class, $this->factory->get('Badge')->getOwner());
35 | }
36 |
37 | /**
38 | * @test
39 | */
40 | public function whenTheOneSideIsASingletonItMayGetSeveralChildObjects()
41 | {
42 | $this->factory->defineEntity('SpaceShip');
43 | $this->factory->defineEntity('Person', [
44 | 'spaceShip' => FieldDef::reference('SpaceShip')
45 | ]);
46 |
47 | $ship = $this->factory->getAsSingleton('SpaceShip');
48 | $p1 = $this->factory->get('Person');
49 | $p2 = $this->factory->get('Person');
50 |
51 | $this->assertContains($p1, $ship->getCrew());
52 | $this->assertContains($p2, $ship->getCrew());
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/DBAL/Types/StatusArrayType.php:
--------------------------------------------------------------------------------
1 | getVarcharTypeDeclarationSQL($fieldDeclaration);
25 | }
26 |
27 | public function convertToDatabaseValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
28 | {
29 | if ($value === null) {
30 | return null;
31 | }
32 |
33 | if (!is_array($value)) {
34 | throw new ConversionException('Value must be an array');
35 | }
36 |
37 | foreach ($value as $val) {
38 | if (!preg_match($this->acceptedPattern, $val)) {
39 | throw new ConversionException("'{$val}' does not match pattern '{$this->acceptedPattern}'");
40 | }
41 | }
42 |
43 | array_walk($value, function (&$walker) {
44 | $walker = '[' . $walker . ']';
45 | });
46 |
47 | return implode(';', $value);
48 | }
49 |
50 | public function convertToPHPValue($value, \Doctrine\DBAL\Platforms\AbstractPlatform $platform)
51 | {
52 | if ($value === null) {
53 | return null;
54 | }
55 |
56 | $ret = explode(';', $value);
57 |
58 | array_walk($ret, function (&$unwashed) {
59 | $unwashed = trim($unwashed, '[]');
60 | });
61 |
62 | return $ret;
63 | }
64 |
65 | public function getName()
66 | {
67 | return self::STATUSARRAY;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/SequenceTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('SpaceShip', [
15 | 'name' => FieldDef::sequence(function ($n) {
16 | return "Alpha $n";
17 | })
18 | ]);
19 | $this->assertSame('Alpha 1', $this->factory->get('SpaceShip')->getName());
20 | $this->assertSame('Alpha 2', $this->factory->get('SpaceShip')->getName());
21 | $this->assertSame('Alpha 3', $this->factory->get('SpaceShip')->getName());
22 | $this->assertSame('Alpha 4', $this->factory->get('SpaceShip')->getName());
23 | }
24 |
25 | /**
26 | * @test
27 | */
28 | public function sequenceGeneratorCanTakeAPlaceholderString()
29 | {
30 | $this->factory->defineEntity('SpaceShip', [
31 | 'name' => FieldDef::sequence("Beta %d")
32 | ]);
33 | $this->assertSame('Beta 1', $this->factory->get('SpaceShip')->getName());
34 | $this->assertSame('Beta 2', $this->factory->get('SpaceShip')->getName());
35 | $this->assertSame('Beta 3', $this->factory->get('SpaceShip')->getName());
36 | $this->assertSame('Beta 4', $this->factory->get('SpaceShip')->getName());
37 | }
38 |
39 | /**
40 | * @test
41 | */
42 | public function sequenceGeneratorCanTakeAStringToAppendTo()
43 | {
44 | $this->factory->defineEntity('SpaceShip', [
45 | 'name' => FieldDef::sequence("Gamma ")
46 | ]);
47 | $this->assertSame('Gamma 1', $this->factory->get('SpaceShip')->getName());
48 | $this->assertSame('Gamma 2', $this->factory->get('SpaceShip')->getName());
49 | $this->assertSame('Gamma 3', $this->factory->get('SpaceShip')->getName());
50 | $this->assertSame('Gamma 4', $this->factory->get('SpaceShip')->getName());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/IncorrectUsageTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('SpaceShip');
12 |
13 | $this->expectException(\Exception::class);
14 |
15 | $factory->defineEntity('SpaceShip');
16 | }
17 |
18 | /**
19 | * @test
20 | */
21 | public function throwsWhenTryingToDefineEntitiesThatAreNotEvenClasses()
22 | {
23 | $this->expectException(\Exception::class);
24 |
25 | $this->factory->defineEntity('NotAClass');
26 | }
27 |
28 | /**
29 | * @test
30 | */
31 | public function throwsWhenTryingToDefineEntitiesThatAreNotEntities()
32 | {
33 | $this->assertTrue(class_exists('FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestEntity\NotAnEntity', true));
34 |
35 | $this->expectException(\Exception::class);
36 |
37 | $this->factory->defineEntity('NotAnEntity');
38 | }
39 |
40 | /**
41 | * @test
42 | */
43 | public function throwsWhenTryingToDefineNonexistentFields()
44 | {
45 | $this->expectException(\Exception::class);
46 |
47 | $this->factory->defineEntity('SpaceShip', [
48 | 'pieType' => 'blueberry'
49 | ]);
50 | }
51 |
52 | /**
53 | * @test
54 | */
55 | public function throwsWhenTryingToGiveNonexistentFieldsWhileConstructing()
56 | {
57 | $this->factory->defineEntity('SpaceShip', ['name' => 'Alpha']);
58 |
59 | $this->expectException(\Exception::class);
60 |
61 | $this->factory->get('SpaceShip', [
62 | 'pieType' => 'blueberry'
63 | ]);
64 | }
65 |
66 | /**
67 | * @test
68 | */
69 | public function throwsWhenTryingToGetLessThanOneInstance()
70 | {
71 | $this->factory->defineEntity('SpaceShip');
72 |
73 | $this->expectException(\Exception::class);
74 |
75 | $this->factory->getList('SpaceShip', [], 0);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/TestDb.php:
--------------------------------------------------------------------------------
1 |
15 | * @license http://www.opensource.org/licenses/BSD-3-Clause New BSD License
16 | */
17 | class TestDb
18 | {
19 | /**
20 | * @var \Doctrine\ORM\Configuration
21 | */
22 | private $doctrineConfig;
23 |
24 | /**
25 | * @var array
26 | */
27 | private $connectionOptions;
28 |
29 | /**
30 | * @param string $annotationPath
31 | * @param string $proxyDir
32 | * @param string $proxyNamespace
33 | */
34 | public function __construct($annotationPath, $proxyDir, $proxyNamespace)
35 | {
36 | $cache = new ArrayCache();
37 |
38 | $config = new Configuration();
39 | $config->setMetadataCacheImpl($cache);
40 | $config->setQueryCacheImpl($cache);
41 | $config->setMetadataDriverImpl(
42 | $config->newDefaultAnnotationDriver($annotationPath)
43 | );
44 | $config->setProxyDir($proxyDir);
45 | $config->setProxyNamespace($proxyNamespace);
46 | $config->setAutoGenerateProxyClasses(true);
47 |
48 | $this->connectionOptions = [
49 | 'driver' => 'pdo_sqlite',
50 | 'path' => ':memory:'
51 | ];
52 |
53 | $this->doctrineConfig = $config;
54 | }
55 |
56 | /**
57 | * @return EntityManager
58 | */
59 | public function createEntityManager()
60 | {
61 | $em = EntityManager::create(
62 | $this->connectionOptions,
63 | $this->doctrineConfig
64 | );
65 | $this->createSchema($em);
66 |
67 | return $em;
68 | }
69 |
70 | /**
71 | * @param EntityManager $em
72 | */
73 | private function createSchema(EntityManager $em)
74 | {
75 | $tool = new SchemaTool($em);
76 | $tool->createSchema($em->getMetadataFactory()->getAllMetadata());
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/SingletonTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('SpaceShip');
12 |
13 | $ss = $this->factory->getAsSingleton('SpaceShip');
14 |
15 | $this->assertSame($ss, $this->factory->get('SpaceShip'));
16 | $this->assertSame($ss, $this->factory->get('SpaceShip'));
17 | }
18 |
19 | /**
20 | * @test
21 | */
22 | public function getAsSingletonMethodAcceptsFieldOverridesLikeGet()
23 | {
24 | $this->factory->defineEntity('SpaceShip');
25 |
26 | $ss = $this->factory->getAsSingleton('SpaceShip', ['name' => 'Beta']);
27 | $this->assertSame('Beta', $ss->getName());
28 | $this->assertSame('Beta', $this->factory->get('SpaceShip')->getName());
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function throwsAnErrorWhenCallingGetSingletonTwiceOnTheSameEntity()
35 | {
36 | $this->factory->defineEntity('SpaceShip', ['name' => 'Alpha']);
37 | $this->factory->getAsSingleton('SpaceShip');
38 |
39 | $this->expectException(\Exception::class);
40 |
41 | $this->factory->getAsSingleton('SpaceShip');
42 | }
43 |
44 | //TODO: should it be an error to get() a singleton with overrides?
45 |
46 | /**
47 | * @test
48 | */
49 | public function allowsSettingSingletons()
50 | {
51 | $this->factory->defineEntity('SpaceShip');
52 | $ss = new TestEntity\SpaceShip("The mothership");
53 |
54 | $this->factory->setSingleton('SpaceShip', $ss);
55 |
56 | $this->assertSame($ss, $this->factory->get('SpaceShip'));
57 | }
58 |
59 | /**
60 | * @test
61 | */
62 | public function allowsUnsettingSingletons()
63 | {
64 | $this->factory->defineEntity('SpaceShip');
65 | $ss = new TestEntity\SpaceShip("The mothership");
66 |
67 | $this->factory->setSingleton('SpaceShip', $ss);
68 | $this->factory->unsetSingleton('SpaceShip');
69 |
70 | $this->assertNotSame($ss, $this->factory->get('SpaceShip'));
71 | }
72 |
73 | /**
74 | * @test
75 | */
76 | public function allowsOverwritingExistingSingletons()
77 | {
78 | $this->factory->defineEntity('SpaceShip');
79 | $ss1 = new TestEntity\SpaceShip("The mothership");
80 | $ss2 = new TestEntity\SpaceShip("The battlecruiser");
81 |
82 | $this->factory->setSingleton('SpaceShip', $ss1);
83 | $this->factory->setSingleton('SpaceShip', $ss2);
84 |
85 | $this->assertSame($ss2, $this->factory->get('SpaceShip'));
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/ORM/DateIntervalHelperTest.php:
--------------------------------------------------------------------------------
1 | expectException(\InvalidArgumentException::class);
20 | $this->expectExceptionMessage(sprintf(
21 | 'Expected integer or integerish string, got "%s" instead.',
22 | is_object($years) ? get_class($years) : gettype($years)
23 | ));
24 |
25 | $helper->years($years);
26 | }
27 |
28 | /**
29 | * @dataProvider providerInvalidIntegerish
30 | *
31 | * @param mixed $months
32 | */
33 | public function testMonthsRejectsInvalidValue($months)
34 | {
35 | $helper = new DateIntervalHelper(new \DateTime());
36 |
37 | $this->expectException(\InvalidArgumentException::class);
38 | $this->expectExceptionMessage(sprintf(
39 | 'Expected integer or integerish string, got "%s" instead.',
40 | is_object($months) ? get_class($months) : gettype($months)
41 | ));
42 |
43 | $helper->months($months);
44 | }
45 |
46 | /**
47 | * @dataProvider providerInvalidIntegerish
48 | *
49 | * @param mixed $days
50 | */
51 | public function testDaysRejectsInvalidValue($days)
52 | {
53 | $helper = new DateIntervalHelper(new \DateTime());
54 |
55 | $this->expectException(\InvalidArgumentException::class);
56 | $this->expectExceptionMessage(sprintf(
57 | 'Expected integer or integerish string, got "%s" instead.',
58 | is_object($days) ? get_class($days) : gettype($days)
59 | ));
60 |
61 | $helper->days($days);
62 | }
63 |
64 | /**
65 | * @return array
66 | */
67 | public function providerInvalidIntegerish()
68 | {
69 | $values = [
70 | 'array' => [],
71 | 'boolean-false' => false,
72 | 'boolean-true' => true,
73 | 'float-negative' => -3.14,
74 | 'float-negative-casted-to-string' => (string) -3.14,
75 | 'float-positive' => 3.14,
76 | 'float-positive-casted-to-string' => (string) 3.14,
77 | 'null' => null,
78 | 'object' => new \stdClass(),
79 | 'resource' => fopen(__FILE__, 'r'),
80 | 'string' => 'foo',
81 | ];
82 |
83 | return \array_map(function ($value) {
84 | return [
85 | $value,
86 | ];
87 | }, $values);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/ReferencesTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('SpaceShip', [
15 | 'crew' => FieldDef::references('Person')
16 | ]);
17 |
18 | $this->factory->defineEntity('Person', [
19 | 'name' => 'Eve',
20 | ]);
21 | }
22 |
23 | /**
24 | * @test
25 | */
26 | public function referencedObjectsShouldBeCreatedAutomatically()
27 | {
28 | /** @var TestEntity\SpaceShip $spaceShip */
29 | $spaceShip = $this->factory->get('SpaceShip');
30 |
31 | $crew = $spaceShip->getCrew();
32 |
33 | $this->assertInstanceOf('Doctrine\Common\Collections\ArrayCollection', $crew);
34 | $this->assertContainsOnly('FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestEntity\Person', $crew);
35 | $this->assertCount(1, $crew);
36 | }
37 |
38 | /**
39 | * @test
40 | */
41 | public function referencedObjectsShouldBeOverrideable()
42 | {
43 | $count = 5;
44 |
45 | /** @var TestEntity\SpaceShip $spaceShip */
46 | $spaceShip = $this->factory->get('SpaceShip', [
47 | 'crew' => $this->factory->getList('Person', [], $count),
48 | ]);
49 |
50 | $crew = $spaceShip->getCrew();
51 |
52 | $this->assertInstanceOf('Doctrine\Common\Collections\ArrayCollection', $crew);
53 | $this->assertContainsOnly('FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestEntity\Person', $crew);
54 | $this->assertCount($count, $crew);
55 | }
56 |
57 | /**
58 | * @test
59 | */
60 | public function referencedObjectsShouldBeNullable()
61 | {
62 | /** @var TestEntity\SpaceShip $spaceShip */
63 | $spaceShip = $this->factory->get('SpaceShip', [
64 | 'crew' => null,
65 | ]);
66 |
67 | $crew = $spaceShip->getCrew();
68 |
69 | $this->assertInstanceOf('Doctrine\Common\Collections\ArrayCollection', $crew);
70 | $this->assertEmpty($crew);
71 | }
72 |
73 | /**
74 | * @test
75 | */
76 | public function referencedObjectsCanBeSingletons()
77 | {
78 | /** @var TestEntity\Person $person*/
79 | $person = $this->factory->getAsSingleton('Person');
80 |
81 | /** @var TestEntity\SpaceShip $spaceShip */
82 | $spaceShip = $this->factory->get('SpaceShip');
83 |
84 | $crew = $spaceShip->getCrew();
85 |
86 | $this->assertInstanceOf('Doctrine\Common\Collections\ArrayCollection', $crew);
87 | $this->assertContains($person, $crew);
88 | $this->assertCount(1, $crew);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/PersistingTest.php:
--------------------------------------------------------------------------------
1 | factory->defineEntity('SpaceShip', ['name' => 'Zeta']);
15 |
16 | $this->factory->persistOnGet();
17 | $ss = $this->factory->get('SpaceShip');
18 | $this->em->flush();
19 |
20 | $this->assertNotNull($ss->getId());
21 | $this->assertSame($ss, $this->em->find('FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestEntity\SpaceShip', $ss->getId()));
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function doesNotPersistByDefault()
28 | {
29 | $this->factory->defineEntity('SpaceShip', ['name' => 'Zeta']);
30 | $ss = $this->factory->get('SpaceShip');
31 | $this->em->flush();
32 |
33 | $this->assertNull($ss->getId());
34 | $q = $this->em
35 | ->createQueryBuilder()
36 | ->select('ss')
37 | ->from('FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestEntity\SpaceShip', 'ss')
38 | ->getQuery();
39 | $this->assertEmpty($q->getResult());
40 | }
41 |
42 | /**
43 | * @test
44 | */
45 | public function doesNotPersistEmbeddableWhenAutomaticPersistingIsTurnedOn()
46 | {
47 | $mappingClasses = [
48 | Mapping\Embeddable::class,
49 | Mapping\Embedded::class,
50 | ];
51 |
52 | foreach ($mappingClasses as $mappingClass) {
53 | if (!class_exists($mappingClass)) {
54 | $this->markTestSkipped('Doctrine Embeddable feature not available');
55 | }
56 | }
57 |
58 | $this->factory->defineEntity('Name', [
59 | 'first' => FieldDef::sequence(static function () {
60 | $values = [
61 | null,
62 | 'Doe',
63 | 'Smith',
64 | ];
65 |
66 | return $values[array_rand($values)];
67 | }),
68 | 'last' => FieldDef::sequence(static function () {
69 | $values = [
70 | null,
71 | 'Jane',
72 | 'John',
73 | ];
74 |
75 | return $values[array_rand($values)];
76 | }),
77 | ]);
78 |
79 | $this->factory->defineEntity('Commander', [
80 | 'name' => FieldDef::reference('Name'),
81 | ]);
82 |
83 | $this->factory->persistOnGet();
84 |
85 | /** @var TestEntity\Commander $commander */
86 | $commander = $this->factory->get('Commander');
87 |
88 | $this->assertInstanceOf(TestEntity\Commander::class, $commander);
89 | $this->assertInstanceOf(TestEntity\Name::class, $commander->name());
90 |
91 | $this->em->flush();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/DateIntervalHelper.php:
--------------------------------------------------------------------------------
1 | time = $time;
27 | $this->negative = $negative;
28 | }
29 |
30 | /**
31 | * @param int $years
32 | * @throws \InvalidArgumentException
33 | * @return $this
34 | */
35 | public function years($years)
36 | {
37 | $this->assertIntegerish($years);
38 |
39 | $this->modify(new \DateInterval('P'.$years.'Y'));
40 |
41 | return $this;
42 | }
43 |
44 | /**
45 | * @param int $months
46 | * @throws \InvalidArgumentException
47 | * @return $this
48 | */
49 | public function months($months)
50 | {
51 | $this->assertIntegerish($months);
52 |
53 | $this->modify(new \DateInterval('P'.$months.'M'));
54 |
55 | return $this;
56 | }
57 |
58 | /**
59 | * @param int $days
60 | * @throws \InvalidArgumentException
61 | * @return $this
62 | */
63 | public function days($days)
64 | {
65 | $this->assertIntegerish($days);
66 |
67 | $this->modify(new \DateInterval('P'.$days.'D'));
68 |
69 | return $this;
70 | }
71 |
72 | private function modify(\DateInterval $interval)
73 | {
74 | $interval->invert = (int) $this->negative;
75 |
76 | $this->time->add($interval);
77 | }
78 |
79 | /**
80 | * @param int $format
81 | * @return \DateTime|int|string
82 | */
83 | public function get($format = self::TIMESTAMP)
84 | {
85 | if ($format == self::DATE_TIME) {
86 | return $this->time;
87 | }
88 |
89 | if ($format == self::TIMESTAMP) {
90 | return $this->time->getTimestamp();
91 | }
92 |
93 | if ($format == self::DATE_STRING) {
94 | return $this->time->format('d-m-y');
95 | }
96 |
97 | throw new \InvalidArgumentException("Unknown time format '". $format ."'");
98 | }
99 |
100 | /**
101 | * @param mixed $value
102 | * @throws \InvalidArgumentException
103 | */
104 | private function assertIntegerish($value)
105 | {
106 | if (!is_numeric($value) || $value != (int)$value) {
107 | throw new \InvalidArgumentException(sprintf(
108 | 'Expected integer or integerish string, got "%s" instead.',
109 | is_object($value) ? get_class($value) : gettype($value)
110 | ));
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Types/StatusArrayTest.php:
--------------------------------------------------------------------------------
1 | platform = $this->getMockForAbstractClass('\Doctrine\DBAL\Platforms\AbstractPlatform');
23 | $this->type = Type::getType('statusarray');
24 | }
25 |
26 | /**
27 | * @test
28 | */
29 | public function getNameShouldReturnExpectedName()
30 | {
31 | $this->assertSame('statusarray', $this->type->getName());
32 | }
33 |
34 | /**
35 | * @test
36 | */
37 | public function nullShouldAlwaysConvertToNull()
38 | {
39 | $this->assertNull($this->type->convertToDatabaseValue(null, $this->platform));
40 | $this->assertNull($this->type->convertToPHPValue(null, $this->platform));
41 | }
42 |
43 | /**
44 | * @test
45 | */
46 | public function nonArrayOrNotNullShouldFailDatabaseConversion()
47 | {
48 | $this->expectException(ConversionException::class);
49 |
50 | $value = 'lussenhof';
51 | $this->type->convertToDatabaseValue($value, $this->platform);
52 | }
53 |
54 | public function provideStupidValues()
55 | {
56 | return [
57 | ['//'],
58 | ['###'],
59 | ['lussenhofen%meister'],
60 | ];
61 | }
62 |
63 | public function provideAcceptableValues()
64 | {
65 | return [
66 | [
67 | '[lussen.hofer];[lussen:meister];[1];[563]',
68 | ['lussen.hofer', 'lussen:meister', 1, 563],
69 | ],
70 | [
71 | '[lussen.hofer]',
72 | ['lussen.hofer'],
73 | ],
74 | ];
75 | }
76 |
77 | /**
78 | * @test
79 | * @dataProvider provideStupidValues
80 | */
81 | public function invalidCharactersShouldFailDatabaseConversion($stupidValue)
82 | {
83 | $this->expectException(ConversionException::class);
84 |
85 | $value = [$stupidValue];
86 | $this->type->convertToDatabaseValue($value, $this->platform);
87 | }
88 |
89 | /**
90 | * @test
91 | * @dataProvider provideAcceptableValues
92 | */
93 | public function acceptableCharactersShouldPassDatabaseConversionAndReturnExpectedSerialization($expectedSerialization, $acceptableValue)
94 | {
95 | $serialization = $this->type->convertToDatabaseValue($acceptableValue, $this->platform);
96 | $this->assertSame($expectedSerialization, $serialization);
97 | }
98 |
99 | public function provideSerializedValues()
100 | {
101 | return [
102 | [
103 | ['lussen', 'hofer', '645', 'meisten:lusdre', 'larva.lussutab.tussi'],
104 | '[lussen];[hofer];[645];[meisten:lusdre];[larva.lussutab.tussi]',
105 | ]
106 | ];
107 | }
108 |
109 | /**
110 | * @test
111 | * @dataProvider provideSerializedValues
112 | */
113 | public function valuesShouldDeserializeProperly($expected, $serialized)
114 | {
115 | $deserialized = $this->type->convertToPHPValue($serialized, $this->platform);
116 | $this->assertSame($expected, $deserialized);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/FieldDef.php:
--------------------------------------------------------------------------------
1 | get($name);
79 | };
80 | }
81 |
82 | /**
83 | * Defines a field to `get()` a collection of named entities from the factory.
84 | *
85 | * The normal semantics of `get()` apply.
86 | *
87 | * Normally this means that the field gets a fresh instance of the named
88 | * entity. If a singleton has been defined, a collection with a single instance will be returned.
89 | *
90 | * @param string $name
91 | * @param int $numberOfInstances
92 | *
93 | * @throws \InvalidArgumentException
94 | * @return callable
95 | */
96 | public static function references($name, $numberOfInstances = 1)
97 | {
98 | if ($numberOfInstances < 1) {
99 | throw new \InvalidArgumentException('Can only get >= 1 instances');
100 | }
101 |
102 | return function (FixtureFactory $factory) use ($name, $numberOfInstances) {
103 | return $factory->getList(
104 | $name,
105 | [],
106 | $numberOfInstances
107 | );
108 | };
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/EntityDef.php:
--------------------------------------------------------------------------------
1 | name = $name;
29 | $this->entityType = $type;
30 | $this->metadata = $em->getClassMetadata($type);
31 | $this->fieldDefs = [];
32 | $this->config = $config;
33 |
34 | $this->readFieldDefs($fieldDefs);
35 | $this->defaultDefsFromMetadata();
36 | }
37 |
38 | private function readFieldDefs(array $params)
39 | {
40 | foreach ($params as $key => $def) {
41 | if ($this->metadata->hasField($key) ||
42 | $this->metadata->hasAssociation($key)) {
43 | $this->fieldDefs[$key] = $this->normalizeFieldDef($def);
44 | } else {
45 | throw new Exception('No such field in ' . $this->entityType . ': ' . $key);
46 | }
47 | }
48 | }
49 |
50 | private function defaultDefsFromMetadata()
51 | {
52 | $defaultEntity = $this->getEntityMetadata()->newInstance();
53 |
54 | $allFields = array_merge($this->metadata->getFieldNames(), $this->metadata->getAssociationNames());
55 | foreach ($allFields as $fieldName) {
56 | if (!isset($this->fieldDefs[$fieldName])) {
57 | $defaultFieldValue = $this->metadata->getFieldValue($defaultEntity, $fieldName);
58 |
59 | if ($defaultFieldValue !== null) {
60 | $this->fieldDefs[$fieldName] = function () use ($defaultFieldValue) {
61 | return $defaultFieldValue;
62 | };
63 | } else {
64 | $this->fieldDefs[$fieldName] = function () {
65 | return null;
66 | };
67 | }
68 | }
69 | }
70 | }
71 |
72 | /**
73 | * Returns the name of the entity definition.
74 | * @return string
75 | */
76 | public function getName()
77 | {
78 | return $this->name;
79 | }
80 |
81 | /**
82 | * Returns the fully qualified name of the entity class.
83 | * @return string
84 | */
85 | public function getEntityType()
86 | {
87 | return $this->entityType;
88 | }
89 |
90 | /**
91 | * Returns the fielde definition callbacks.
92 | */
93 | public function getFieldDefs()
94 | {
95 | return $this->fieldDefs;
96 | }
97 |
98 | /**
99 | * Returns the Doctrine metadata for the entity to be created.
100 | * @return ClassMetadata
101 | */
102 | public function getEntityMetadata()
103 | {
104 | return $this->metadata;
105 | }
106 |
107 | /**
108 | * Returns the extra configuration array of the entity definition.
109 | * @return array
110 | */
111 | public function getConfig()
112 | {
113 | return $this->config;
114 | }
115 |
116 | private function normalizeFieldDef($def)
117 | {
118 | if (is_callable($def)) {
119 | return $this->ensureInvokable($def);
120 | }
121 |
122 | return function () use ($def) {
123 | return $def;
124 | };
125 | }
126 |
127 | private function ensureInvokable($f)
128 | {
129 | if (method_exists($f, '__invoke')) {
130 | return $f;
131 | }
132 |
133 | return function () use ($f) {
134 | return call_user_func_array($f, func_get_args());
135 | };
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/ORM/QueryBuilder.php:
--------------------------------------------------------------------------------
1 | boolean>
15 | */
16 | protected $_statuses = [];
17 |
18 | /**
19 | * @var array
20 | */
21 | protected $_queryConfigurers = [];
22 |
23 | /**
24 | * Entity name associated with this query builder (if any)
25 | *
26 | * @var string
27 | */
28 | protected $entityName;
29 |
30 | /**
31 | * Entity alias associated with this query builder (if any)
32 | *
33 | * @var string
34 | */
35 | protected $entityAlias;
36 |
37 | /**
38 | * Create a new QueryBuilder of the type that receives this static call.
39 | *
40 | * @param EntityManager $em
41 | * @return QueryBuilder
42 | */
43 | public static function create(EntityManager $em)
44 | {
45 | return new static($em);
46 | }
47 |
48 | /**
49 | * @param EntityManager $em
50 | * @param string $entityName
51 | * @param string $entityAlias
52 | */
53 | public function __construct(EntityManager $em, $entityName = null, $entityAlias = null)
54 | {
55 | parent::__construct($em);
56 |
57 | $this->entityName = $entityName;
58 | $this->entityAlias = $entityAlias;
59 | $this->init();
60 | }
61 |
62 | /**
63 | * Post-construction template method
64 | *
65 | * Prepopulates with entity if both entity name and alias are set.
66 | */
67 | public function init()
68 | {
69 | if (is_string($this->entityName) && is_string($this->entityAlias)) {
70 | $this->select($this->entityAlias)
71 | ->from($this->entityName, $this->entityAlias);
72 | }
73 | }
74 |
75 | /**
76 | * Configures the created Query using the configurers added to this builder
77 | *
78 | * @return \Doctrine\ORM\Query
79 | */
80 | public function getQuery()
81 | {
82 | $query = parent::getQuery();
83 | foreach ($this->_queryConfigurers as $configurer) {
84 | $configurer($query);
85 | }
86 | return $query;
87 | }
88 |
89 | /**
90 | * NOTE: Should be protected
91 | *
92 | * @param callback(Doctrine\ORM\Query) $configurer
93 | * @return QueryBuilder
94 | */
95 | public function _configureQuery($configurer)
96 | {
97 | $this->_queryConfigurers[] = $configurer;
98 | return $this;
99 | }
100 |
101 | /**
102 | * Ensure that a certain operation has been performed on this object.
103 | * $status is a string that describes state the operation should affect and
104 | * $operation is a callback which is executed if and only if the status is
105 | * not already in effect.
106 | *
107 | * Example:
108 | *
109 | * public function withProduct() {
110 | * return $this->ensure('product is joined', function($qb) {
111 | * $qb->join('pv.product', 'p');
112 | * });
113 | * }
114 | *
115 | * Should be used to allow the possibility of several different methods
116 | * each wanting to affect a certain state without messing up the query by
117 | * duplicate calls to methods. Has the pleasant side effect of describing
118 | * the results of operations on a more intimate level than the plain object
119 | * API can allow for, making for more self-documenting code.
120 | *
121 | * @param string $status
122 | * @param callback(QueryBuilder) $operation
123 | * @return QueryBuilder
124 | */
125 | protected function ensure($status, $operation)
126 | {
127 | if (empty($this->_statuses[$status])) {
128 | $operation($this);
129 | $this->_statuses[$status] = true;
130 | }
131 | return $this;
132 | }
133 |
134 | /**
135 | * Configures the query to force result objects to be partially loaded
136 | *
137 | * @return QueryBuilder
138 | */
139 | protected function asPartial()
140 | {
141 | return $this->ensure('partial loading is forced', function (QueryBuilder $builder) {
142 | $builder->_configureQuery(function (Query $query) {
143 | $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
144 | });
145 | });
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/ORM/Locking/TableLock.php:
--------------------------------------------------------------------------------
1 | repository = $repository;
25 | }
26 |
27 | /**
28 | * @return Repository
29 | */
30 | protected function getRepository()
31 | {
32 | return $this->repository;
33 | }
34 |
35 | /**
36 | * Attempt to acquire a table level lock in MySQL for the duration of the
37 | * given transaction. IS NOT IN ANY WAY GUARANTEED TO WORK. MySQL requires
38 | * that the aliases through which a table is accessed during this
39 | * transaction are enumerated when locking tables, which due to the nature
40 | * of Doctrine is a somewhat difficult task. Nevertheless, in simple cases a
41 | * good guesstimate as to the table aliases can be made; see relevant
42 | * methods below.
43 | *
44 | * @param int $lockMode a TableLockMode constant
45 | * @param callback $transaction
46 | * @return mixed
47 | * @throws LockException
48 | */
49 | public function transaction($lockMode, $transaction)
50 | {
51 | $lock = $this->getLockString($lockMode);
52 | $unlock = $this->getUnlockString();
53 |
54 | return $this->getRepository()->transaction(function (EntityManager $em, Repository $repository) use ($lock, $unlock, $transaction) {
55 | $conn = $em->getConnection();
56 | $conn->executeQuery($lock);
57 | try {
58 | $result = $repository->transaction($transaction);
59 | $conn->executeQuery($unlock);
60 | return $result;
61 | } catch (Exception $e) {
62 | // Transaction rollback does not release table locks
63 | $conn->executeQuery($unlock);
64 | throw $e;
65 | }
66 | });
67 | }
68 |
69 | /**
70 | * Get the MySQL statement for locking the table underlying this repository
71 | * for simple read and/or write operations given an appropriate lock mode
72 | *
73 | * @param int $lockMode a TableLockMode constant
74 | * @return string
75 | * @throws LockException
76 | */
77 | private function getLockString($lockMode)
78 | {
79 | $lockModeString = TableLockMode::toString($lockMode);
80 | if (!$lockModeString) {
81 | throw new LockException("Invalid lock mode: $lockMode");
82 | }
83 |
84 | $tableName = $this->getTableName();
85 | $aliases = $this->getTableAliasGuesstimates($tableName);
86 |
87 | return $this->constructLockString($tableName, $aliases, $lockModeString);
88 | }
89 |
90 | /**
91 | * @return string
92 | */
93 | private function getTableName()
94 | {
95 | // Blatant violation of law of demeter
96 | return $this->getRepository()->getClassMetadata()->getTableName();
97 | }
98 |
99 | /**
100 | * @param string $tableName
101 | * @param array $aliases
102 | * @param string $lockModeString
103 | * @return string
104 | */
105 | private function constructLockString($tableName, array $aliases, $lockModeString)
106 | {
107 | $lock = "LOCK TABLES $tableName $lockModeString";
108 | foreach ($aliases as $alias) {
109 | $lock .= ", $tableName as $alias $lockModeString";
110 | }
111 | return $lock;
112 | }
113 |
114 | /**
115 | * Attempt to guess at the table name aliases used by Doctrine for a given
116 | * table name
117 | *
118 | * @param string $tableName
119 | * @return array
120 | */
121 | private function getTableAliasGuesstimates($tableName)
122 | {
123 | return array_unique([
124 | // the default generated alias: the first letter of the table name prepended with a zero
125 | strtolower(substr($tableName, 0, 1)) . '0',
126 | // a generic alias used by Doctrine in many cases
127 | 't0'
128 | ]);
129 | }
130 |
131 | /**
132 | * The MySQL statement required to unlock tables after a transaction
133 | *
134 | * @return string
135 | */
136 | private function getUnlockString()
137 | {
138 | return 'UNLOCK TABLES';
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/tests/Provider/Doctrine/Fixtures/BasicUsageTest.php:
--------------------------------------------------------------------------------
1 | factory
15 | ->defineEntity('SpaceShip', [
16 | 'name' => 'My BattleCruiser'
17 | ])
18 | ->get('SpaceShip');
19 |
20 | $this->assertSame('My BattleCruiser', $ss->getName());
21 | }
22 |
23 | /**
24 | * @test
25 | */
26 | public function acceptsGeneratorFunctionsInEntityDefinitions()
27 | {
28 | $name = "Star";
29 | $this->factory->defineEntity('SpaceShip', [
30 | 'name' => function () use (&$name) {
31 | return "M/S $name";
32 | }
33 | ]);
34 |
35 | $this->assertSame('M/S Star', $this->factory->get('SpaceShip')->getName());
36 | $name = "Superstar";
37 | $this->assertSame('M/S Superstar', $this->factory->get('SpaceShip')->getName());
38 | }
39 |
40 | /**
41 | * @test
42 | */
43 | public function valuesCanBeOverriddenAtCreationTime()
44 | {
45 | $ss = $this->factory
46 | ->defineEntity('SpaceShip', [
47 | 'name' => 'My BattleCruiser'
48 | ])
49 | ->get('SpaceShip', ['name' => 'My CattleBruiser']);
50 | $this->assertSame('My CattleBruiser', $ss->getName());
51 | }
52 |
53 | /**
54 | * @test
55 | */
56 | public function preservesDefaultValuesOfEntity()
57 | {
58 | $ss = $this->factory
59 | ->defineEntity('SpaceStation')
60 | ->get('SpaceStation');
61 | $this->assertSame('Babylon5', $ss->getName());
62 | }
63 |
64 | /**
65 | * @test
66 | */
67 | public function doesNotCallTheConstructorOfTheEntity()
68 | {
69 | $ss = $this->factory
70 | ->defineEntity('SpaceShip', [])
71 | ->get('SpaceShip');
72 | $this->assertFalse($ss->constructorWasCalled());
73 | }
74 |
75 | /**
76 | * @test
77 | */
78 | public function instantiatesCollectionAssociationsToBeEmptyCollectionsWhenUnspecified()
79 | {
80 | $ss = $this->factory
81 | ->defineEntity('SpaceShip', [
82 | 'name' => 'Battlestar Galaxy'
83 | ])
84 | ->get('SpaceShip');
85 |
86 | $this->assertInstanceOf(ArrayCollection::class, $ss->getCrew());
87 | $this->assertEmpty($ss->getCrew());
88 | }
89 |
90 | /**
91 | * @test
92 | */
93 | public function arrayElementsAreMappedToCollectionAsscociationFields()
94 | {
95 | $this->factory->defineEntity('SpaceShip');
96 | $this->factory->defineEntity('Person', [
97 | 'spaceShip' => FieldDef::reference('SpaceShip')
98 | ]);
99 |
100 | $p1 = $this->factory->get('Person');
101 | $p2 = $this->factory->get('Person');
102 |
103 | $ship = $this->factory->get('SpaceShip', [
104 | 'name' => 'Battlestar Galaxy',
105 | 'crew' => [$p1, $p2]
106 | ]);
107 |
108 | $this->assertInstanceOf(ArrayCollection::class, $ship->getCrew());
109 | $this->assertTrue($ship->getCrew()->contains($p1));
110 | $this->assertTrue($ship->getCrew()->contains($p2));
111 | }
112 |
113 | /**
114 | * @test
115 | */
116 | public function unspecifiedFieldsAreLeftNull()
117 | {
118 | $this->factory->defineEntity('SpaceShip');
119 | $this->assertNull($this->factory->get('SpaceShip')->getName());
120 | }
121 |
122 | /**
123 | * @test
124 | */
125 | public function entityIsDefinedToDefaultNamespace()
126 | {
127 | $this->factory->defineEntity('SpaceShip');
128 | $this->factory->defineEntity('Person\User');
129 |
130 | $this->assertInstanceOf(
131 | 'FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestEntity\SpaceShip',
132 | $this->factory->get('SpaceShip')
133 | );
134 |
135 | $this->assertInstanceOf(
136 | 'FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestEntity\Person\User',
137 | $this->factory->get('Person\User')
138 | );
139 | }
140 |
141 | /**
142 | * @test
143 | */
144 | public function entityCanBeDefinedToAnotherNamespace()
145 | {
146 | $this->factory->defineEntity(
147 | '\FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestAnotherEntity\Artist'
148 | );
149 |
150 | $this->assertInstanceOf(
151 | 'FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestAnotherEntity\Artist',
152 | $this->factory->get(
153 | '\FactoryGirl\Tests\Provider\Doctrine\Fixtures\TestAnotherEntity\Artist'
154 | )
155 | );
156 | }
157 |
158 | /**
159 | * @test
160 | */
161 | public function returnsListOfEntities()
162 | {
163 | $this->factory->defineEntity('SpaceShip');
164 |
165 | $this->assertCount(1, $this->factory->getList('SpaceShip'));
166 | }
167 |
168 | /**
169 | * @test
170 | */
171 | public function canSpecifyNumberOfReturnedInstances()
172 | {
173 | $this->factory->defineEntity('SpaceShip');
174 |
175 | $this->assertCount(5, $this->factory->getList('SpaceShip', [], 5));
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Factory Girl in PHP
2 | ===================
3 |
4 | [](https://github.com/unhashable/factory-girl-php/actions)
5 |
6 | A PHP port of Thoughtbot's Ruby [Factory Girl](https://github.com/thoughtbot/factory_girl). Based on a fork of [xi-doctrine](https://github.com/xi-project/xi-doctrine).
7 |
8 |
9 | FactoryGirl FixtureFactory
10 | --------------
11 |
12 | `FactoryGirl\Provider\Doctrine\FixtureFactory` provides convenient creation of Doctrine entities in tests. If you're familiar with [FactoryGirl](https://github.com/thoughtbot/factory_girl) for Ruby, then this is essentially the same thing for Doctrine/PHP.
13 |
14 | ### Motivation ###
15 |
16 | Many web applications have non-trivial database structures with lots of dependencies between tables. A component of such an application might deal with entities from only one or two tables, but the entities may depend on a complex entity graph to be useful or pass validation.
17 |
18 | For instance, a `User` may be a member of a `Group`, which is part of an `Organization`, which in turn depends on five different tables describing who-knows-what about the organization. You are writing a component that change's the user's password and are currently uninterested in groups, organizations and their dependencies. How do you set up your test?
19 |
20 | 1. Do you create all dependencies for `Organization` and `Group` to get a valid `User` in your `setUp()`? No, that would be horribly tedious and verbose.
21 | 2. Do you make a shared fixture for all your tests that includes an example organization with satisifed dependencies? No, that would make the fixture extremely fragile.
22 | 3. Do you use mock objects? Sure, where practical. In many cases, however, the code you're testing interacts with the entities in such a complex way that mocking them sufficiently is impractical.
23 |
24 | `FixtureFactory` is a middle ground between *(1)* and *(2)*. You specify how to generate your entities and their dependencies in one central place but explicitly create them in your tests, overriding only the fields you want.
25 |
26 | ### Tutorial ###
27 |
28 | We'll assume you have a base class for your tests that arranges a fresh `EntityManager` connected to a minimally initialized blank test database. A simple factory setup looks like this.
29 |
30 | ```php
31 | entityManager) ...
44 |
45 | $this->factory = new FixtureFactory($this->entityManager);
46 | $this->factory->setEntityNamespace('What\Ever'); // If applicable
47 |
48 | // Define that users have names like user_1, user_2, etc.,
49 | // that they are not administrators by default and
50 | // that they point to a Group entity.
51 | $this->factory->defineEntity('User', [
52 | 'username' => FieldDef::sequence("user_%d"),
53 | 'administrator' => false,
54 | 'group' => FieldDef::reference('Group')
55 | ]);
56 |
57 | // Define a Group to just have a unique name as above.
58 | // The order of the definitions does not matter.
59 | $this->factory->defineEntity('Group', [
60 | 'name' => FieldDef::sequence("group_%d")
61 | ]);
62 |
63 |
64 | // If you want your created entities to be saved by default
65 | // then do the following. You can selectively re-enable or disable
66 | // this behavior in each test as well.
67 | // It's recommended to only enable this in tests that need it.
68 | // In any case, you'll need to call flush() yourself.
69 | //$this->factory->persistOnGet();
70 | }
71 | }
72 | ```
73 |
74 | Now you can easily get entities and override fields relevant to your test case like this.
75 |
76 | ```php
77 | factory->get('User', [
86 | 'name' => 'John'
87 | ]);
88 | $this->service->changePassword($user, 'xoo');
89 | $this->assertSame($user, $this->service->authenticateUser('john', 'xoo'));
90 | }
91 | }
92 | ```
93 |
94 | ### Singletons ###
95 |
96 | Sometimes your entity has a dependency graph with several references to some entity type. For instance, the application may have a concept of a "current organization" with users, groups, products, categories etc. belonging to an organization. By default `FixtureFactory` would create a new `Organization` each time one is needed, which is not always what you want. Sometimes you'd like each new entity to point to one shared `Organization`.
97 |
98 | Your first reaction should be to avoid situations like this and specify the shared entity explicitly when you can't. If that isn't feasible for whatever reason, `FixtureFactory` allows you to make an entity a *singleton*. If a singleton exists for a type of entity then `get()` will return that instead of creating a new instance.
99 |
100 | ```php
101 | org = $this->factory->getAsSingleton('Organization');
109 | }
110 |
111 | public function testSomething(): void
112 | {
113 | $user1 = $this->factory->get('User');
114 | $user2 = $this->factory->get('User');
115 |
116 | // now $user1->getOrganization() === $user2->getOrganization() ...
117 | }
118 | }
119 | ```
120 |
121 | It's highly recommended to create singletons only in the setups of individual test classes and *NOT* in the base class of your tests.
122 |
123 | ### Advanced ###
124 |
125 | You can give an 'afterCreate' callback to be called after an entity is created and its fields are set. Here you can, for instance, invoke the entity's constructor, since `FixtureFactory` doesn't do that by default.
126 |
127 | ```php
128 | defineEntity(
131 | 'User',
132 | [
133 | 'username' => FieldDef::sequence("user_%d"),
134 | ],
135 | [
136 | 'afterCreate' => function(User $user, array $fieldValues) {
137 | $user->__construct($fieldValues['username']);
138 | }
139 | ]);
140 | ```
141 |
142 | ### API reference ###
143 |
144 | ```php
145 | defineEntity(
149 | 'EntityName',
150 | [
151 | 'simpleField' => 'constantValue',
152 |
153 | 'generatedField' => function($factory) { return ...; },
154 |
155 | 'sequenceField1' => FieldDef::sequence('name-%d'), // name-1, name-2, ...
156 | 'sequenceField2' => FieldDef::sequence('name-'), // the same
157 | 'sequenceField3' => FieldDef::sequence(function($n) { return "name-$n"; }),
158 |
159 | 'referenceField' => FieldDef::reference('OtherEntity')
160 | ],
161 | [
162 | 'afterCreate' => function($entity, $fieldValues) {
163 | // ...
164 | }
165 | ]
166 | );
167 |
168 | // Getting an entity (new or singleton)
169 | $factory->get('EntityName', ['field' => 'value']);
170 |
171 | // Getting an array of entities
172 | $numberOfEntities = 15;
173 | $factory->getList('EntityName', ['field' => 'value'], $numberOfEntities);
174 |
175 | // Singletons
176 | $factory->getAsSingleton('EntityName', ['field' => 'value']);
177 | $factory->setSingleton('EntityName', $entity);
178 | $factory->unsetSingleton('EntityName');
179 |
180 | // Configuration
181 | $this->factory->setEntityNamespace('What\Ever'); // Default: empty
182 | $this->factory->persistOnGet(); // Default: don't persist
183 | $this->factory->persistOnGet(false);
184 | ```
185 |
186 | ### Miscellaneous ###
187 |
188 | - `FixtureFactory` and `FieldDef` are designed to be subclassable.
189 | - With bidirectional one-to-many associations, the collection on the 'one'
190 | side will get updated as long as you've remembered to specify the
191 | `inversedBy` attribute in your mapping.
192 |
193 | ### Development ###
194 |
195 | #### Tests ####
196 |
197 | The composer packages must be installed with
198 |
199 | ```
200 | composer install --prefer-source
201 | ```
202 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/ORM/Repository.php:
--------------------------------------------------------------------------------
1 | getQuery()->getResult();
22 | }
23 |
24 | /**
25 | * @param \Doctrine\ORM\QueryBuilder $builder
26 | * @param callback(Exception) $fallback optional
27 | * @return object | null result or return value from fallback
28 | */
29 | protected function getSingleQueryResult($builder, $fallback = null)
30 | {
31 | return $this->attemptQuery(function () use ($builder) {
32 | return $builder->getQuery()->getSingleResult();
33 | }, $fallback);
34 | }
35 |
36 | /**
37 | * @param \Doctrine\ORM\QueryBuilder $builder
38 | * @param callback(Exception) $fallback optional
39 | * @return object | null result or return value from fallback
40 | */
41 | protected function getSingleScalarQueryResult($builder, $fallback = null)
42 | {
43 | return $this->attemptQuery(function () use ($builder) {
44 | return $builder->getQuery()->getSingleScalarResult();
45 | }, $fallback);
46 | }
47 |
48 | /**
49 | * Guards against NoResultException and NonUniqueResultException within a
50 | * callback. Uses a fallback callback in case an exception does occur.
51 | *
52 | * @param callback $do
53 | * @param callback $fallback optional
54 | * @return mixed
55 | */
56 | private function attemptQuery($do, $fallback = null)
57 | {
58 | if (null === $fallback) {
59 | $fallback = function () {
60 | };
61 | }
62 | try {
63 | return $do();
64 | } catch (\Doctrine\ORM\NoResultException $e) {
65 | return $fallback($e);
66 | } catch (\Doctrine\ORM\NonUniqueResultException $e) {
67 | return $fallback($e);
68 | }
69 | }
70 |
71 | /**
72 | * Create a query builder, perform the given operation on it and return the
73 | * query builder. The operation callback receives the query builder and its
74 | * associated expression builder as arguments.
75 | *
76 | * @param callback(QueryBuilder, Doctrine\ORM\Query\Expr) $do
77 | * @return QueryBuilder
78 | */
79 | protected function withQueryBuilder($do)
80 | {
81 | $qb = $this->getBaseQueryBuilder();
82 | $do($qb, $qb->expr());
83 | return $qb;
84 | }
85 |
86 | /**
87 | * Create a query builder. Override this in a child class to create a
88 | * builder of the appropriate type.
89 | *
90 | * @return QueryBuilder
91 | */
92 | protected function getBaseQueryBuilder()
93 | {
94 | return QueryBuilder::create($this->_em);
95 | }
96 |
97 | /**
98 | * Gets a reference to the entity identified by the given type and identifier
99 | * without actually loading it, if the entity is not yet loaded.
100 | *
101 | * @param mixed $identifier The entity identifier.
102 | * @return object The entity reference.
103 | */
104 | public function getReference($identifier)
105 | {
106 | return $this->getEntityManager()
107 | ->getReference($this->getEntityName(), $identifier);
108 | }
109 |
110 | /**
111 | * Perform a callback function within a transaction. If an exception occurs
112 | * within the function, it's catched, the transaction is rolled back and
113 | * the exception rethrown.
114 | *
115 | * @param callback(Doctrine\ORM\EntityManager, Repository) $transaction
116 | * @return mixed the callback return value
117 | * @throws Exception
118 | */
119 | public function transaction($transaction)
120 | {
121 | $em = $this->getEntityManager();
122 | $conn = $em->getConnection();
123 |
124 | $conn->beginTransaction();
125 | try {
126 | $result = $transaction($em, $this);
127 | $em->flush();
128 | $conn->commit();
129 | return $result;
130 | } catch (Exception $e) {
131 | $em->close();
132 | $conn->rollback();
133 | throw $e;
134 | }
135 | }
136 |
137 | /**
138 | * Acquires a lock to an entity, provides the entity to a callback function
139 | * and relinquishes the lock by flushing the entity manager immediately
140 | * after.
141 | *
142 | * @param int $id
143 | * @param int $lockMode
144 | * @param callback($entity, Doctrine\ORM\EntityManager, Repository) $callback
145 | * @return mixed callback return type
146 | * @throws LockException
147 | */
148 | public function useWithLock($id, $lockMode, $callback)
149 | {
150 | $entityName = $this->getEntityName();
151 | return $this->transaction(function ($em, $self) use ($id, $lockMode, $callback, $entityName) {
152 | $entity = $self->find($id, $lockMode);
153 | if (empty($entity)) {
154 | $message = \sprintf("Could not lock %s entity by id %d: entity not found", $entityName, $id);
155 | throw new LockException($message);
156 | }
157 | $result = $callback($entity, $em, $self);
158 | return $result;
159 | });
160 | }
161 |
162 | /**
163 | * Calls useWithLock() with a pessimistic write lock mode
164 | *
165 | * @param int $id
166 | * @param callback($entity, Doctrine\ORM\EntityManager, Repository) $callback
167 | * @return mixed callback return type
168 | */
169 | public function useWithPessimisticWriteLock($id, $callback)
170 | {
171 | return $this->useWithLock($id, LockMode::PESSIMISTIC_WRITE, $callback);
172 | }
173 |
174 | /**
175 | * Acquires an optimistic lock within a pessimistic lock transaction. For
176 | * use in fail-fast scenarios; guaranteed to throw an exception on
177 | * concurrent modification attempts. The one to first acquire the write lock
178 | * will update the version field, leading subsequent acquisitions of the
179 | * optimistic lock to fail.
180 | *
181 | * FIXME: Only works on entities implementing VersionLockable and does not
182 | * work in conjunction with the Doctrine @Version column.
183 | *
184 | * @param int $id
185 | * @param mixed $lockVersion
186 | * @param callback($entity, Doctrine\ORM\EntityManager, Repository) $callback
187 | * @return mixed callback return type
188 | * @throws OptimisticLockException
189 | */
190 | public function useWithPessimisticVersionLock($id, $lockVersion, $callback)
191 | {
192 | return $this->useWithPessimisticWriteLock($id, function (VersionLockable $entity, EntityManager $em, $self) use ($lockVersion, $callback) {
193 | if ($entity->getVersion() !== $lockVersion) {
194 | // FIXME: This isn't the appropriate exception type.
195 | throw OptimisticLockException::lockFailedVersionMissmatch($entity, $lockVersion, $entity->getVersion());
196 | }
197 | $entity->incrementVersion();
198 | return $callback($entity, $em, $self);
199 | });
200 | }
201 |
202 | /**
203 | * @return string
204 | */
205 | public function getEntityName()
206 | {
207 | return parent::getEntityName();
208 | }
209 |
210 | /**
211 | * Attempt to acquire a table level lock in MySQL for the duration of the
212 | * given transaction. IS NOT IN ANY WAY GUARANTEED TO WORK.
213 | *
214 | * @see TableLock
215 | * @param int $lockMode a TableLockMode constant
216 | * @param callback $transaction
217 | * @return mixed
218 | * @throws LockException
219 | */
220 | public function transactionWithTableLock($lockMode, $transaction)
221 | {
222 | return $this->getTableLock()->transaction($lockMode, $transaction);
223 | }
224 |
225 | /**
226 | * @return TableLock
227 | */
228 | private function getTableLock()
229 | {
230 | return new TableLock($this);
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/Provider/Doctrine/FixtureFactory.php:
--------------------------------------------------------------------------------
1 |
29 | */
30 | protected $entityDefs;
31 |
32 | /**
33 | * @var array
34 | */
35 | protected $singletons;
36 |
37 | /**
38 | * @var boolean
39 | */
40 | protected $persist;
41 |
42 | public function __construct(EntityManager $em)
43 | {
44 | $this->em = $em;
45 |
46 | $this->entityNamespace = '';
47 |
48 | $this->entityDefs = [];
49 |
50 | $this->singletons = [];
51 |
52 | $this->persist = false;
53 | }
54 |
55 | /**
56 | * Sets the namespace to be prefixed to all entity names passed to this class.
57 | */
58 | public function setEntityNamespace($namespace)
59 | {
60 | $this->entityNamespace = trim($namespace, '\\');
61 | }
62 |
63 | public function getEntityNamespace()
64 | {
65 | return $this->entityNamespace;
66 | }
67 |
68 | /**
69 | * Get an entity and its dependencies.
70 | *
71 | * Whether the entity is new or not depends on whether you've created
72 | * a singleton with the entity name. See `getAsSingleton()`.
73 | *
74 | * If you've called `persistOnGet()` then the entity is also persisted.
75 | *
76 | * @throws EntityDefinitionUnavailable
77 | */
78 | public function get($name, array $fieldOverrides = [])
79 | {
80 | if (isset($this->singletons[$name])) {
81 | return $this->singletons[$name];
82 | }
83 |
84 | if (!array_key_exists($name, $this->entityDefs)) {
85 | throw EntityDefinitionUnavailable::for($name);
86 | }
87 |
88 | /** @var EntityDef $def */
89 | $def = $this->entityDefs[$name];
90 |
91 | $config = $def->getConfig();
92 |
93 | $this->checkFieldOverrides($def, $fieldOverrides);
94 |
95 | /** @var Mapping\ClassMetadata $entityMetadata */
96 | $entityMetadata = $def->getEntityMetadata();
97 |
98 | $ent = $entityMetadata->newInstance();
99 | $fieldValues = [];
100 | foreach ($def->getFieldDefs() as $fieldName => $fieldDef) {
101 | $fieldValues[$fieldName] = array_key_exists($fieldName, $fieldOverrides)
102 | ? $fieldOverrides[$fieldName]
103 | : $fieldDef($this);
104 | }
105 |
106 | foreach ($fieldValues as $fieldName => $fieldValue) {
107 | $this->setField($ent, $def, $fieldName, $fieldValue);
108 | }
109 |
110 | if (isset($config['afterCreate'])) {
111 | $config['afterCreate']($ent, $fieldValues);
112 | }
113 |
114 | if ($this->persist && false === $entityMetadata->isEmbeddedClass) {
115 | $this->em->persist($ent);
116 | }
117 |
118 | return $ent;
119 | }
120 |
121 | /**
122 | * Get an array of entities and their dependencies.
123 | *
124 | * Whether the entities are new or not depends on whether you've created
125 | * a singleton with the entity name. See `getAsSingleton()`.
126 | *
127 | * If you've called `persistOnGet()` then the entities are also persisted.
128 | */
129 | public function getList($name, array $fieldOverrides = [], $numberOfInstances = 1)
130 | {
131 | if ($numberOfInstances < 1) {
132 | throw new \InvalidArgumentException('Can only get >= 1 instances');
133 | }
134 |
135 | if ($numberOfInstances > 1 && array_key_exists($name, $this->singletons)) {
136 | $numberOfInstances = 1;
137 | }
138 |
139 | $instances = [];
140 | for ($i = 0; $i < $numberOfInstances; $i++) {
141 | $instances[] = $this->get($name, $fieldOverrides);
142 | }
143 |
144 | return $instances;
145 | }
146 |
147 | protected function checkFieldOverrides(EntityDef $def, array $fieldOverrides)
148 | {
149 | $extraFields = array_diff(array_keys($fieldOverrides), array_keys($def->getFieldDefs()));
150 | if (!empty($extraFields)) {
151 | throw new Exception("Field(s) not in " . $def->getEntityType() . ": '" . implode("', '", $extraFields) . "'");
152 | }
153 | }
154 |
155 | protected function setField($ent, EntityDef $def, $fieldName, $fieldValue)
156 | {
157 | $metadata = $def->getEntityMetadata();
158 |
159 | if ($metadata->isCollectionValuedAssociation($fieldName)) {
160 | $metadata->setFieldValue($ent, $fieldName, $this->createCollectionFrom($fieldValue));
161 | } else {
162 | $metadata->setFieldValue($ent, $fieldName, $fieldValue);
163 |
164 | if (is_object($fieldValue) && $metadata->isSingleValuedAssociation($fieldName)) {
165 | $this->updateCollectionSideOfAssocation($ent, $metadata, $fieldName, $fieldValue);
166 | }
167 | }
168 | }
169 |
170 | protected function createCollectionFrom($array = [])
171 | {
172 | if (is_array($array)) {
173 | return new ArrayCollection($array);
174 | }
175 |
176 | return new ArrayCollection();
177 | }
178 |
179 | /**
180 | * Sets whether `get()` should automatically persist the entity it creates.
181 | * By default it does not. In any case, you still need to call
182 | * flush() yourself.
183 | */
184 | public function persistOnGet($enabled = true)
185 | {
186 | $this->persist = $enabled;
187 | }
188 |
189 | /**
190 | * A shorthand combining `get()` and `setSingleton()`.
191 | *
192 | * It's illegal to call this if `$name` already has a singleton.
193 | */
194 | public function getAsSingleton($name, array $fieldOverrides = [])
195 | {
196 | if (isset($this->singletons[$name])) {
197 | throw new Exception("Already a singleton: $name");
198 | }
199 | $this->singletons[$name] = $this->get($name, $fieldOverrides);
200 | return $this->singletons[$name];
201 | }
202 |
203 | /**
204 | * Sets `$entity` to be the singleton for `$name`.
205 | *
206 | * This causes `get($name)` to return `$entity`.
207 | */
208 | public function setSingleton($name, $entity)
209 | {
210 | $this->singletons[$name] = $entity;
211 | }
212 |
213 | /**
214 | * Unsets the singleton for `$name`.
215 | *
216 | * This causes `get($name)` to return new entities again.
217 | */
218 | public function unsetSingleton($name)
219 | {
220 | unset($this->singletons[$name]);
221 | }
222 |
223 | /**
224 | * Defines how to create a default entity of type `$name`.
225 | *
226 | * See the readme for a tutorial.
227 | *
228 | * @return FixtureFactory
229 | */
230 | public function defineEntity($name, array $fieldDefs = [], array $config = [])
231 | {
232 | if (isset($this->entityDefs[$name])) {
233 | throw new Exception("Entity '$name' already defined in fixture factory");
234 | }
235 |
236 | $type = $this->addNamespace($name);
237 | if (!class_exists($type, true)) {
238 | throw new Exception("Not a class: $type");
239 | }
240 |
241 | $metadata = $this->em->getClassMetadata($type);
242 | if (!isset($metadata)) {
243 | throw new Exception("Unknown entity type: $type");
244 | }
245 |
246 | $this->entityDefs[$name] = new EntityDef($this->em, $name, $type, $fieldDefs, $config);
247 |
248 | return $this;
249 | }
250 |
251 | /**
252 | * @param string $name
253 | * @return string
254 | */
255 | protected function addNamespace($name)
256 | {
257 | $name = rtrim($name, '\\');
258 |
259 | if ($name[0] === '\\') {
260 | return $name;
261 | }
262 |
263 | return $this->entityNamespace . '\\' . $name;
264 | }
265 |
266 | protected function updateCollectionSideOfAssocation($entityBeingCreated, $metadata, $fieldName, $value)
267 | {
268 | $assoc = $metadata->getAssociationMapping($fieldName);
269 | $inverse = $assoc['inversedBy'];
270 | if ($inverse) {
271 | $valueMetadata = $this->em->getClassMetadata(get_class($value));
272 | $collection = $valueMetadata->getFieldValue($value, $inverse);
273 | if ($collection instanceof Collection) {
274 | $collection->add($entityBeingCreated);
275 | }
276 | }
277 | }
278 | }
279 |
--------------------------------------------------------------------------------