From 39d838141f68733f510e751fa55dc4aeee07250b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Thu, 25 May 2017 13:24:41 +0200 Subject: [PATCH] Enhancement: Provide Definition interface and easy way to find and register them --- .php_cs | 4 +- README.md | 74 +++++++++++- composer.json | 14 ++- src/.gitkeep | 0 src/Definition.php | 19 ++++ src/Definitions.php | 87 ++++++++++++++ src/Exception/InvalidDefinition.php | 33 ++++++ src/Exception/InvalidDirectory.php | 41 +++++++ .../Definition/Acceptable/UserDefinition.php | 26 +++++ .../CanNotBeAutoloaded/UserDefinition.php | 26 +++++ .../UserDefinition.php | 25 ++++ .../Definition/IsAbstract/UserDefinition.php | 27 +++++ .../UserDefinition.php | 32 ++++++ test/Unit/Asset/Entity/User.php | 16 +++ test/Unit/DefinitionsTest.php | 107 ++++++++++++++++++ test/Unit/Exception/InvalidDefinitionTest.php | 47 ++++++++ test/Unit/Exception/InvalidDirectoryTest.php | 61 ++++++++++ test/Unit/ProjectCodeTest.php | 31 +++++ 18 files changed, 665 insertions(+), 5 deletions(-) delete mode 100644 src/.gitkeep create mode 100644 src/Definition.php create mode 100644 src/Definitions.php create mode 100644 src/Exception/InvalidDefinition.php create mode 100644 src/Exception/InvalidDirectory.php create mode 100644 test/Unit/Asset/Definition/Acceptable/UserDefinition.php create mode 100644 test/Unit/Asset/Definition/CanNotBeAutoloaded/UserDefinition.php create mode 100644 test/Unit/Asset/Definition/DoesNotImplementInterface/UserDefinition.php create mode 100644 test/Unit/Asset/Definition/IsAbstract/UserDefinition.php create mode 100644 test/Unit/Asset/Definition/ThrowsExceptionDuringConstruction/UserDefinition.php create mode 100644 test/Unit/Asset/Entity/User.php create mode 100644 test/Unit/DefinitionsTest.php create mode 100644 test/Unit/Exception/InvalidDefinitionTest.php create mode 100644 test/Unit/Exception/InvalidDirectoryTest.php create mode 100644 test/Unit/ProjectCodeTest.php diff --git a/.php_cs b/.php_cs index 77a9ebc..5921158 100644 --- a/.php_cs +++ b/.php_cs @@ -13,7 +13,9 @@ EOF; $config = Config\Factory::fromRuleSet(new Config\RuleSet\Php56($header)); -$config->getFinder()->in(__DIR__); +$config->getFinder() + ->in(__DIR__) + ->exclude('test/Unit/Asset/Definition/CanNotBeAutoloaded'); $cacheDir = \getenv('TRAVIS') ? \getenv('HOME') . '/.php-cs-fixer' : __DIR__; diff --git a/README.md b/README.md index 1c9cc99..2be8706 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ [![Latest Stable Version](https://poser.pugx.org/localheinz/factory-girl-definition/v/stable)](https://packagist.org/packages/localheinz/factory-girl-definition) [![Total Downloads](https://poser.pugx.org/localheinz/factory-girl-definition/downloads)](https://packagist.org/packages/localheinz/factory-girl-definition) +Provides an interface for, and an easy way to find and register entity definitions for [`breerly/factory-girl-php`](https://github.com/breerly/factory-girl-php). + ## Installation Run @@ -17,7 +19,77 @@ $ composer require localheinz/factory-girl-definition ## Usage -:bulb: This is a great place for showing a few usage examples! +### Create Definitions + +Implement the `Definition` interface and use the instance of `FactoryGirl\Provider\Doctrine\FixtureFactory` +that is passed in into `accept()` to define entities: + +```php +defineEntity(Entity\User::class, [ + // ... + ]); + } +} +``` + +:bulb: Any number of entities can be defined within a definition. +However, it's probably a good idea to create a definition for each entity. + +### Register Definitions + +Lazily instantiate an instance of `FactoryGirl\Provider\Doctrine\FixtureFactory` +and use `Definitions` to find definitions and register them with the factory: + +```php +persistOnGet(true); + + Definitions::in(__DIR__ . '/../Fixture')->registerWith($fixtureFactory); + } + + return $fixtureFactory; + } +} +``` ## Contributing diff --git a/composer.json b/composer.json index 7ae0411..329adb3 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "localheinz/factory-girl-definition", - "description": "Provides an interface and a finder for definitions with breerly/factory-girl-php.", + "description": "Provides an interface for, and an easy way to find and register entity definitions for breerly/factory-girl-php.", "type": "library", "license": "MIT", "authors": [ @@ -13,13 +13,21 @@ "preferred-install": "dist", "sort-packages": true }, + "minimum-stability": "beta", + "prefer-stable": true, "require": { - "php": "^5.6 || ^7.0" + "php": "^5.6 || ^7.0", + "zendframework/zend-file": "^2.7.1" }, "require-dev": { + "breerly/factory-girl-php": "^1.0.0", "codeclimate/php-test-reporter": "0.4.4", "localheinz/php-cs-fixer-config": "1.2.1", - "phpunit/phpunit": "^5.7.20" + "phpunit/phpunit": "^5.7.20", + "refinery29/test-util": "^0.11.3" + }, + "suggest": { + "breerly/factory-girl-php": "For creating a fixture factory the definitions can be used with." }, "autoload": { "psr-4": { diff --git a/src/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/Definition.php b/src/Definition.php new file mode 100644 index 0000000..d50921f --- /dev/null +++ b/src/Definition.php @@ -0,0 +1,19 @@ +getClasses() as $className) { + try { + $reflection = new \ReflectionClass($className); + } catch (\ReflectionException $exception) { + continue; + } + + if (!$reflection->isSubclassOf(Definition::class) || $reflection->isAbstract()) { + continue; + } + + try { + $definition = $reflection->newInstance(); + } catch (\Exception $exception) { + throw Exception\InvalidDefinition::fromClassNameAndException( + $className, + $exception + ); + } + + $instance->definitions[] = $definition; + } + } + + return $instance; + } + + public function registerWith(FixtureFactory $fixtureFactory) + { + foreach ($this->definitions as $definition) { + $definition->accept($fixtureFactory); + } + } +} diff --git a/src/Exception/InvalidDefinition.php b/src/Exception/InvalidDefinition.php new file mode 100644 index 0000000..7e829ce --- /dev/null +++ b/src/Exception/InvalidDefinition.php @@ -0,0 +1,33 @@ +defineEntity('Foo'); + } +} diff --git a/test/Unit/Asset/Definition/CanNotBeAutoloaded/UserDefinition.php b/test/Unit/Asset/Definition/CanNotBeAutoloaded/UserDefinition.php new file mode 100644 index 0000000..7465443 --- /dev/null +++ b/test/Unit/Asset/Definition/CanNotBeAutoloaded/UserDefinition.php @@ -0,0 +1,26 @@ +defineEntity('Foo'); + } +} diff --git a/test/Unit/Asset/Definition/DoesNotImplementInterface/UserDefinition.php b/test/Unit/Asset/Definition/DoesNotImplementInterface/UserDefinition.php new file mode 100644 index 0000000..4edff40 --- /dev/null +++ b/test/Unit/Asset/Definition/DoesNotImplementInterface/UserDefinition.php @@ -0,0 +1,25 @@ +defineEntity('Foo'); + } +} diff --git a/test/Unit/Asset/Definition/IsAbstract/UserDefinition.php b/test/Unit/Asset/Definition/IsAbstract/UserDefinition.php new file mode 100644 index 0000000..1127cab --- /dev/null +++ b/test/Unit/Asset/Definition/IsAbstract/UserDefinition.php @@ -0,0 +1,27 @@ +defineEntity(Entity\User::class); + } +} diff --git a/test/Unit/Asset/Definition/ThrowsExceptionDuringConstruction/UserDefinition.php b/test/Unit/Asset/Definition/ThrowsExceptionDuringConstruction/UserDefinition.php new file mode 100644 index 0000000..2aed139 --- /dev/null +++ b/test/Unit/Asset/Definition/ThrowsExceptionDuringConstruction/UserDefinition.php @@ -0,0 +1,32 @@ +defineEntity(Entity\User::class); + } +} diff --git a/test/Unit/Asset/Entity/User.php b/test/Unit/Asset/Entity/User.php new file mode 100644 index 0000000..5df7d9c --- /dev/null +++ b/test/Unit/Asset/Entity/User.php @@ -0,0 +1,16 @@ +expectException(Exception\InvalidDirectory::class); + + Definitions::in($directory); + } + + public function testInRejectsNonExistentDirectory() + { + $this->expectException(Exception\InvalidDirectory::class); + + Definitions::in(__DIR__ . '/Asset/Definition/NonExistentDirectory'); + } + + public function testInIgnoresClassesWhichCanNotBeAutoloaded() + { + $fixtureFactory = $this->createFixtureFactoryMock(); + + $fixtureFactory + ->expects($this->never()) + ->method($this->anything()); + + Definitions::in(__DIR__ . '/Asset/Definition/CanNotBeAutoloaded')->registerWith($fixtureFactory); + } + + public function testInIgnoresClassesWhichDoNotImplementProviderInterface() + { + $fixtureFactory = $this->createFixtureFactoryMock(); + + $fixtureFactory + ->expects($this->never()) + ->method($this->anything()); + + Definitions::in(__DIR__ . '/Asset/Definition/DoesNotImplementInterface')->registerWith($fixtureFactory); + } + + public function testInIgnoresClassesWhichAreAbstract() + { + $fixtureFactory = $this->createFixtureFactoryMock(); + + $fixtureFactory + ->expects($this->never()) + ->method($this->anything()); + + Definitions::in(__DIR__ . '/Asset/Definition/IsAbstract')->registerWith($fixtureFactory); + } + + public function testInAcceptsClassesWhichAreAcceptable() + { + $fixtureFactory = $this->createFixtureFactoryMock(); + + $fixtureFactory + ->expects($this->once()) + ->method('defineEntity'); + + Definitions::in(__DIR__ . '/Asset/Definition/Acceptable')->registerWith($fixtureFactory); + } + + public function testThrowsInvalidDefinitionExceptionIfInstantiatingDefinitionsThrowsException() + { + $fixtureFactory = $this->createFixtureFactoryMock(); + + $fixtureFactory + ->expects($this->never()) + ->method($this->anything()); + + $this->expectException(Exception\InvalidDefinition::class); + + Definitions::in(__DIR__ . '/Asset/Definition/ThrowsExceptionDuringConstruction'); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|FixtureFactory + */ + private function createFixtureFactoryMock() + { + return $this->createMock(FixtureFactory::class); + } +} diff --git a/test/Unit/Exception/InvalidDefinitionTest.php b/test/Unit/Exception/InvalidDefinitionTest.php new file mode 100644 index 0000000..e62d8a1 --- /dev/null +++ b/test/Unit/Exception/InvalidDefinitionTest.php @@ -0,0 +1,47 @@ +assertExtends(\RuntimeException::class, Exception\InvalidDefinition::class); + } + + public function testFromClassNameCreatesException() + { + $className = $this->getFaker()->word; + $previousException = new \Exception(); + + $exception = Exception\InvalidDefinition::fromClassNameAndException( + $className, + $previousException + ); + + $this->assertInstanceOf(Exception\InvalidDefinition::class, $exception); + + $message = \sprintf( + 'An exception was thrown while trying to instantiate definition "%s".', + $className + ); + + $this->assertSame($message, $exception->getMessage()); + $this->assertSame($previousException, $exception->getPrevious()); + } +} diff --git a/test/Unit/Exception/InvalidDirectoryTest.php b/test/Unit/Exception/InvalidDirectoryTest.php new file mode 100644 index 0000000..2574eda --- /dev/null +++ b/test/Unit/Exception/InvalidDirectoryTest.php @@ -0,0 +1,61 @@ +assertExtends(\InvalidArgumentException::class, Exception\InvalidDirectory::class); + } + + /** + * @dataProvider \Refinery29\Test\Util\DataProvider\InvalidString::data() + * + * @param mixed $directory + */ + public function testNotStringCreatesException($directory) + { + $exception = Exception\InvalidDirectory::notString($directory); + + $this->assertInstanceOf(Exception\InvalidDirectory::class, $exception); + + $message = \sprintf( + 'Directory should be a string, got %s instead.', + \is_object($directory) ? \get_class($directory) : \gettype($directory) + ); + + $this->assertSame($message, $exception->getMessage()); + } + + public function testNotDirectoryCreatesException() + { + $directory = $this->getFaker()->word; + + $exception = Exception\InvalidDirectory::notDirectory($directory); + + $this->assertInstanceOf(Exception\InvalidDirectory::class, $exception); + + $message = \sprintf( + 'Directory should be a directory, but "%s" is not.', + $directory + ); + + $this->assertSame($message, $exception->getMessage()); + } +} diff --git a/test/Unit/ProjectCodeTest.php b/test/Unit/ProjectCodeTest.php new file mode 100644 index 0000000..b8a0381 --- /dev/null +++ b/test/Unit/ProjectCodeTest.php @@ -0,0 +1,31 @@ +assertClassesAreAbstractOrFinal(__DIR__ . '/../../src'); + } + + public function testTestCodeIsAbstractOrFinal() + { + $this->assertClassesAreAbstractOrFinal(__DIR__ . '/..', [ + 'Unit/Asset/Definition/CanNotBeAutoloaded', + ]); + } +}