diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ead574
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,6 @@
+/tests export-ignore
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/.travis.yml export-ignore
+/phpunit.xml.dist export-ignore
+/README.md export-ignore
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f5b93f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+/vendor
+/composer.lock
+###> phpunit/phpunit ###
+/phpunit.xml
+/build
+/.phpunit.result.cache
+/tests/app/cache
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f7bd39a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,18 @@
+language: php
+php:
+ - 7.2
+
+install:
+ - travis_retry composer self-update
+ - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction
+ - curl -s http://getcomposer.org/installer | php
+ - php composer.phar install --dev --no-interaction
+
+script:
+ - mkdir -p build/logs
+ - php vendor/bin/phpunit -c phpunit.xml.dist
+
+after_success:
+ - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar --output-document="${HOME}/bin/coveralls"
+ - chmod u+x "${HOME}/bin/coveralls"
+ - coveralls -v
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..23c875a
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,50 @@
+{
+ "name": "pfilsx/form-layer-bundle",
+ "description": "Provides additional functional to follow rule \"An entity should be always valid\" with forms validation for your Symfony project",
+ "type": "symfony-bundle",
+ "keywords": [
+ "form",
+ "symfony",
+ "symfony4",
+ "symfony5",
+ "bundle",
+ "entity",
+ "orm",
+ "doctrine"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Pavel Filimonov",
+ "email": "pfilsx@gmail.com"
+ }
+ ],
+ "minimum-stability": "stable",
+ "require": {
+ "php": ">=7.1",
+ "symfony/framework-bundle": "^3.4|^4.0|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+ "symfony/console": "^3.4|^4.0|^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.0",
+ "phpunit/php-code-coverage": "^7.0",
+ "symfony/maker-bundle": "*",
+ "symfony/phpunit-bridge": "^5.0",
+ "symfony/process": "^3.4|^4.0|^5.0",
+ "symfony/validator": "^3.4|^4.0|^5.0",
+ "symfony/yaml": "^3.4|^4.0|^5.0",
+ "symfony/routing": "^3.4|^4.0|^5.0",
+ "symfony/orm-pack": "*"
+ },
+ "autoload": {
+ "psr-4": {
+ "Pfilsx\\FormLayer\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Pfilsx\\FormLayer\\Tests\\": "tests"
+ }
+ }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..778d8c2
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ./tests
+
+
+
+
+
+ ./src
+
+ ./src
+ ./src
+ ./src/DependencyInjection
+
+
+
+
+
+
+
+
diff --git a/src/DependencyInjection/FormLayerExtension.php b/src/DependencyInjection/FormLayerExtension.php
new file mode 100644
index 0000000..0f746e0
--- /dev/null
+++ b/src/DependencyInjection/FormLayerExtension.php
@@ -0,0 +1,18 @@
+load('services.xml');
+ }
+}
diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..bba3270
--- /dev/null
+++ b/src/Exception/InvalidArgumentException.php
@@ -0,0 +1,11 @@
+registerForAutoconfiguration(FormLayerInterface::class)->addTag('form_layer.type');
+ }
+}
diff --git a/src/Layer/EntityFormLayer.php b/src/Layer/EntityFormLayer.php
new file mode 100644
index 0000000..3e1dbe8
--- /dev/null
+++ b/src/Layer/EntityFormLayer.php
@@ -0,0 +1,108 @@
+entity !== null) {
+ if (method_exists($this->entity, 'getId')) {
+ return $this->entity->getId();
+ }
+ if (property_exists($this->entity, 'id')) {
+ return $this->entity->id;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function load($entity)
+ {
+ if (!is_a($entity, static::getEntityClass())) {
+ throw new InvalidArgumentException('Expected instance of ' . static::getEntityClass() . ', got ' . get_class($entity));
+ }
+ $this->entity = $entity;
+ $this->loadLayerFields();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function create(bool $force = false)
+ {
+ if ($force || $this->entity === null) {
+ $className = static::getEntityClass();
+ $this->entity = new $className();
+ }
+ $this->update();
+ return $this->entity;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function update()
+ {
+ if ($this->entity !== null) {
+ $this->loadEntityFields();
+ }
+ }
+
+ /**
+ * Loads data from associated entity
+ */
+ protected function loadLayerFields()
+ {
+ foreach (get_object_vars($this) as $prop => $val) {
+ $getter = 'get' . $prop;
+ $value = $val;
+ if (method_exists($this->entity, $getter)) {
+ $value = $this->entity->$getter();
+ } elseif (property_exists($this->entity, $prop)) {
+ $value = $this->entity->$prop;
+ }
+ $loadMethod = 'load' . $prop;
+ if (method_exists($this, $loadMethod)) {
+ $this->$loadMethod($value);
+ } else {
+ $this->$prop = $value;
+ }
+ }
+ }
+
+ /**
+ * Saves data into associated entity
+ */
+ protected function loadEntityFields()
+ {
+ foreach (get_object_vars($this) as $prop => $value) {
+ $saveMethod = 'save' . $prop;
+ $value = method_exists($this, $saveMethod) ? $this->$saveMethod() : $this->$prop;
+ $setter = 'set' . $prop;
+ if (method_exists($this->entity, $setter)) {
+ $this->entity->$setter($value);
+ } elseif (property_exists($this->entity, $prop)) {
+ $this->entity->$prop = $value;
+ }
+ }
+ }
+}
diff --git a/src/Layer/FormLayerInterface.php b/src/Layer/FormLayerInterface.php
new file mode 100644
index 0000000..1fac799
--- /dev/null
+++ b/src/Layer/FormLayerInterface.php
@@ -0,0 +1,27 @@
+entityHelper = $entityHelper;
+ $this->formLayerRenderer = $formLayerRenderer;
+ }
+
+ public static function getCommandName(): string
+ {
+ return 'make:form-layer';
+ }
+
+ public function configureCommand(Command $command, InputConfiguration $inputConfig)
+ {
+ $command
+ ->setDescription('Creates a new form layer class')
+ ->addArgument('name', InputArgument::OPTIONAL, sprintf('The name of the form layer class (e.g. %sFormLayer>)', Str::asClassName(Str::getRandomTerm())))
+ ->addArgument('bound-class', InputArgument::OPTIONAL, 'The name of Entity or fully qualified model class name that the new form will be bound to (empty for none)');
+ $inputConfig->setArgumentAsNonInteractive('bound-class');
+ }
+
+ public function configureDependencies(DependencyBuilder $dependencies)
+ {
+ $dependencies->addClassDependency(
+ DoctrineBundle::class,
+ 'orm'
+ );
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param ConsoleStyle $io
+ * @param Command $command
+ *
+ * @codeCoverageIgnore
+ */
+ public function interact(InputInterface $input, ConsoleStyle $io, Command $command)
+ {
+ if (null === $input->getArgument('bound-class')) {
+ $argument = $command->getDefinition()->getArgument('bound-class');
+ $entities = $this->entityHelper->getEntitiesForAutocomplete();
+ $question = new Question($argument->getDescription());
+ $question->setValidator(function ($answer) use ($entities) {
+ return Validator::existsOrNull($answer, $entities);
+ });
+ $question->setAutocompleterValues($entities);
+ $question->setMaxAttempts(3);
+ $input->setArgument('bound-class', $io->askQuestion($question));
+ }
+ }
+
+ public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
+ {
+ $formClassNameDetails = $generator->createClassNameDetails(
+ $input->getArgument('name'),
+ 'FormLayer\\',
+ 'FormLayer'
+ );
+
+ $formFields = ['form_field'];
+
+ $boundClass = $input->getArgument('bound-class');
+ $boundClassDetails = null;
+ if (null !== $boundClass) {
+ $formFields = [];
+ $boundClassDetails = $generator->createClassNameDetails(
+ $boundClass,
+ 'Entity\\'
+ );
+ $doctrineMetadata = $this->entityHelper->getMetadata($boundClassDetails->getFullName());
+ if ($doctrineMetadata instanceof ClassMetadata) {
+ foreach ($doctrineMetadata->getFieldNames() as $fieldName) {
+ $formFields[] = $fieldName;
+ }
+ foreach ($doctrineMetadata->associationMappings as $fieldName => $relation) {
+ if ($relation['type'] === ClassMetadata::MANY_TO_ONE) {
+ $formFields[] = $fieldName;
+ }
+ }
+ } else {
+ $reflect = new ReflectionClass($boundClassDetails->getFullName());
+ foreach ($reflect->getProperties() as $prop) {
+ $formFields[] = $prop->getName();
+ }
+ }
+ }
+
+ $this->formLayerRenderer->render(
+ $formClassNameDetails,
+ $formFields,
+ $boundClassDetails
+ );
+ $generator->writeChanges();
+ $this->writeSuccessMessage($io);
+ $io->text('Next: Add fields to your form layer and start using it.');
+ }
+}
diff --git a/src/Renderer/FormLayerRenderer.php b/src/Renderer/FormLayerRenderer.php
new file mode 100644
index 0000000..e3c9d76
--- /dev/null
+++ b/src/Renderer/FormLayerRenderer.php
@@ -0,0 +1,34 @@
+generator = $generator;
+ }
+
+ public function render(ClassNameDetails $formClassDetails, array $formFields, ClassNameDetails $boundClassDetails = null)
+ {
+ $this->generator->generateClass(
+ $formClassDetails->getFullName(),
+ __DIR__ . '/../Resources/skeleton/FormLayer.tpl.php',
+ [
+ 'bounded_full_class_name' => $boundClassDetails ? $boundClassDetails->getFullName() : null,
+ 'bounded_class_name' => $boundClassDetails ? $boundClassDetails->getShortName() : null,
+ 'form_fields' => $formFields
+ ]
+ );
+ }
+}
diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml
new file mode 100644
index 0000000..7fb61e1
--- /dev/null
+++ b/src/Resources/config/services.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ Pfilsx\FormLayer\Renderer\FormLayerRenderer
+ Pfilsx\FormLayer\Maker\MakeFormLayer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Resources/doc/index.rst b/src/Resources/doc/index.rst
new file mode 100644
index 0000000..bce10eb
--- /dev/null
+++ b/src/Resources/doc/index.rst
@@ -0,0 +1,35 @@
+Getting started with FormLayerBundle
+========================================
+
+Overview
+--------
+
+The bundle integrates special layer between your doctrine entities and forms for `Symfony`_ project. It
+automatically registers all FormLayer classes as services, so you can wire it.
+
+Here, an example how to use FormLayer:
+
+.. code-block:: php
+
+ use App\FormLayer\CustomFormLayer;
+
+ ...
+
+ public function update(CustomFormLayer $layer, CustomEntity $entity): Response
+ {
+ $layer->load($entity);
+ $form = $this->createForm(CustomType::class, $layer);
+ // ... your code with form and if form is submitted and valid
+ $layer->update(); // updates props in associated entity
+ $this->getDoctrine()->getManager()->flush(); // for example
+ return $this->render('entity/index.html.twig', [
+ 'form' => $form->createView()
+ ]);
+ }
+
+.. toctree::
+
+ installation
+ usage
+
+.. _`Symfony`: http://symfony.com/
diff --git a/src/Resources/doc/installation.rst b/src/Resources/doc/installation.rst
new file mode 100644
index 0000000..dab3f3e
--- /dev/null
+++ b/src/Resources/doc/installation.rst
@@ -0,0 +1,45 @@
+Installation
+============
+
+Step 1: Download the Bundle
+---------------------------
+
+Open a command console, enter your project directory and execute the following
+command to download the latest stable version of this bundle:
+
+.. code-block:: bash
+
+ $ composer require pfilsx/form-layer-bundle
+
+This command requires you to have Composer installed globally, as explained
+in the `installation chapter`_ of the Composer documentation.
+
+Step 2: Enable the Bundle
+-------------------------
+
+Then, enable the bundle by adding the following line in the ``app/AppKernel.php``
+file of your project:
+
+.. code-block:: php
+
+ field2; // by get... method you can customize how layer should send data to form
+ }
+
+ public function setField2($value)
+ {
+ $this->field2 = $value; // by set... method you can customize how layer should take data from form
+ return $this;
+ }
+
+ public function loadField2(\DateTime $value)
+ {
+ $this->field2 = $value->format('d.m.Y'); // by load... method you can customize how layer should take data from entity
+ }
+
+ public function saveField2()
+ {
+ return new \DateTime($this->field2); // by save... method you can customize how layer should send data to entity
+ }
+ }
+
+Step 3: Use your layer in your controller
+----------------------------------------
+
+.. code-block:: php
+
+ public function update(CustomFormLayer $layer, CustomEntity $entity): Response
+ {
+ $layer->load($entity);
+ $form = $this->createForm(CustomType::class, $layer);
+ // ... your code with form and if form is submitted and valid
+ $layer->update(); // updates props in associated entity
+ $this->getDoctrine()->getManager()->flush(); // for example
+ return $this->render('entity/index.html.twig', [
+ 'form' => $form->createView()
+ ]);
+ }
diff --git a/src/Resources/skeleton/FormLayer.tpl.php b/src/Resources/skeleton/FormLayer.tpl.php
new file mode 100644
index 0000000..7ec7e8e
--- /dev/null
+++ b/src/Resources/skeleton/FormLayer.tpl.php
@@ -0,0 +1,28 @@
+= "
+
+namespace = $namespace ?>;
+
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+use Pfilsx\FormLayer\Layer\EntityFormLayer;
+
+
+use = $bounded_full_class_name ?>;
+
+/**
+* @method = $bounded_class_name ?> create(bool $force = false)
+* @method void load(= $bounded_class_name ?> $entity)
+*/
+
+class = $class_name ?> extends EntityFormLayer
+{
+
+ public $= $form_field ?>;
+
+
+ public static function getEntityClass(): string
+ {
+ return = !empty($bounded_class_name) ? "$bounded_class_name::class" : "''" ?>;
+ }
+}
diff --git a/tests/FormLayerBundleTest.php b/tests/FormLayerBundleTest.php
new file mode 100644
index 0000000..e95b720
--- /dev/null
+++ b/tests/FormLayerBundleTest.php
@@ -0,0 +1,20 @@
+build($container);
+ $this->assertEquals('FormLayerBundle', $bundle->getName());
+ }
+}
diff --git a/tests/KernelTestCase.php b/tests/KernelTestCase.php
new file mode 100644
index 0000000..db1478d
--- /dev/null
+++ b/tests/KernelTestCase.php
@@ -0,0 +1,32 @@
+application = new Application($kernel);
+ $this->application->setAutoExit(false);
+ $this->application->run(new ArrayInput(array(
+ 'doctrine:schema:drop',
+ '--force' => true
+ )));
+ $this->application->run(new ArrayInput(array(
+ 'doctrine:schema:create'
+ )));
+ }
+}
diff --git a/tests/Layer/EntityFormLayerTest.php b/tests/Layer/EntityFormLayerTest.php
new file mode 100644
index 0000000..45e6dc2
--- /dev/null
+++ b/tests/Layer/EntityFormLayerTest.php
@@ -0,0 +1,121 @@
+assertNull($layer->getId());
+ $this->assertNull($layer->createdAt);
+ $this->assertNull($layer->content);
+
+ $layer->createdAt = '01.01.1970';
+ $node = $layer->create();
+ $this->assertInstanceOf($entityClass, $node);
+ $this->assertEquals(new DateTime('01.01.1970'), $useMethod ? $node->getCreatedAt() : $node->createdAt);
+ }
+
+ /**
+ * @dataProvider getLayers
+ * @param string $formLayerClass
+ */
+ public function testWrongLoad($formLayerClass)
+ {
+ $node = new class {
+ };
+ /**
+ * @var NodeFormLayer|ModelFormLayer $layer
+ */
+ $layer = new $formLayerClass();
+ $this->expectException(InvalidArgumentException::class);
+ $layer->load($node);
+ }
+
+ /**
+ * @dataProvider getLayers
+ * @param string $formLayerClass
+ * @param string $entityClass
+ */
+ public function testLoad($formLayerClass, $entityClass, $useMethod)
+ {
+ /**
+ * @var Node|Model $node
+ */
+ $node = new $entityClass();
+ if ($useMethod) {
+ $node->setCreatedAt(new DateTime('01.01.1970'))
+ ->setId(1)->setContent('Test content');
+ } else {
+ $node->createdAt = new DateTime('01.01.1970');
+ $node->id = 1;
+ $node->content = 'Test content';
+ }
+ /**
+ * @var NodeFormLayer|ModelFormLayer $layer
+ */
+ $layer = new $formLayerClass();
+ $layer->load($node);
+ $this->assertEquals(1, $layer->getId());
+ $this->assertEquals('01.01.1970', $layer->createdAt);
+ $this->assertEquals('Test content', $layer->content);
+
+ $layer->createdAt = '02.01.1970';
+ $layer->update();
+ $this->assertEquals(new DateTime('02.01.1970'), $useMethod ? $node->getCreatedAt() : $node->createdAt);
+ }
+
+ /**
+ * @dataProvider getLayers
+ * @param string $formLayerClass
+ * @param string $entityClass
+ */
+ public function testForceCreate($formLayerClass, $entityClass)
+ {
+ /**
+ * @var Node|Model $node
+ */
+ $node = new $entityClass();
+ /**
+ * @var NodeFormLayer|ModelFormLayer $layer
+ */
+ $layer = new $formLayerClass();
+ $layer->load($node);
+ $this->assertSame($node, $layer->create());
+ $this->assertNotSame($node, $layer->create(true));
+ }
+
+ public function getLayers()
+ {
+ yield [
+ NodeFormLayer::class,
+ Node::class,
+ true
+ ];
+ yield [
+ ModelFormLayer::class,
+ Model::class,
+ false
+ ];
+ }
+}
diff --git a/tests/Maker/FunctionalTest.php b/tests/Maker/FunctionalTest.php
new file mode 100644
index 0000000..7deb868
--- /dev/null
+++ b/tests/Maker/FunctionalTest.php
@@ -0,0 +1,88 @@
+app_path = dirname(__DIR__) . '/app';
+ parent::setUp();
+ }
+
+ public function testWiring()
+ {
+ $class = MakeFormLayer::class;
+ $commandName = $class::getCommandName();
+ $this->assertEquals('make:form-layer', $commandName);
+ $command = $this->application->find($commandName);
+ $this->assertInstanceOf(MakerCommand::class, $command);
+ }
+
+ /**
+ * @dataProvider getCommands
+ * @param $name
+ * @param $entity
+ * @param $result
+ */
+ public function testMaker($name, $entity, $result)
+ {
+ $input = new StringInput("make:form-layer $name $entity");
+ $output = new BufferedOutput(OutputInterface::VERBOSITY_NORMAL, true);
+ $this->application->run($input, $output);
+ $filePath = $this->app_path . "/FormLayer/$name.php";
+ $this->assertTrue(is_file($filePath));
+ $layerClass = "Pfilsx\\FormLayer\\Tests\\app\\FormLayer\\$name";
+ $layer = new $layerClass();
+ $this->assertInstanceOf(EntityFormLayer::class, $layer);
+ $this->assertEquals($result, get_object_vars($layer));
+ @unlink($filePath);
+ }
+
+ public function getCommands()
+ {
+ yield [
+ 'FooBarTestFormLayer',
+ null,
+ ['form_field' => null]
+ ];
+ yield [
+ 'NodeTestFormLayer',
+ 'Node',
+ [
+ 'id' => null,
+ 'content' => null,
+ 'user' => null,
+ 'parentId' => null,
+ 'createdAt' => null,
+ 'mainNode' => null
+ ]
+ ];
+ yield [
+ 'ModelTestFormLayer',
+ 'Model',
+ [
+ 'id' => null,
+ 'content' => null,
+ 'createdAt' => null
+ ]
+ ];
+ }
+}
diff --git a/tests/app/AppKernel.php b/tests/app/AppKernel.php
new file mode 100644
index 0000000..7290ee0
--- /dev/null
+++ b/tests/app/AppKernel.php
@@ -0,0 +1,44 @@
+load(__DIR__ . '/config/config_' . $this->getEnvironment() . '.yml');
+ }
+
+ public function getCacheDir()
+ {
+ return __DIR__ . '/cache/' . $this->environment;
+ }
+}
diff --git a/tests/app/Entity/Model.php b/tests/app/Entity/Model.php
new file mode 100644
index 0000000..38ff8f1
--- /dev/null
+++ b/tests/app/Entity/Model.php
@@ -0,0 +1,14 @@
+content = $content;
+ return $this;
+ }
+
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ public function setCreatedAt($createdAt)
+ {
+ $this->createdAt = $createdAt;
+ return $this;
+ }
+
+ public function getCreatedAt()
+ {
+ return $this->createdAt;
+ }
+
+ public function setId($id)
+ {
+ $this->id = $id;
+ return $this;
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function setParentId($parentId)
+ {
+ $this->parentId = $parentId;
+ return $this;
+ }
+
+ public function getParentId()
+ {
+ return $this->parentId;
+ }
+
+ public function setUser($user)
+ {
+ $this->user = $user;
+ return $this;
+ }
+
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ public function setMainNode($mainNode)
+ {
+ $this->mainNode = $mainNode;
+ return $this;
+ }
+
+ public function getMainNode()
+ {
+ return $this->mainNode;
+ }
+
+ public function setSubNodeList($subNodeList)
+ {
+ $this->subNodeList = $subNodeList;
+ return $this;
+ }
+
+ public function getSubNodeList()
+ {
+ return $this->subNodeList;
+ }
+}
diff --git a/tests/app/FormLayer/ModelFormLayer.php b/tests/app/FormLayer/ModelFormLayer.php
new file mode 100644
index 0000000..4eca08b
--- /dev/null
+++ b/tests/app/FormLayer/ModelFormLayer.php
@@ -0,0 +1,30 @@
+createdAt = $val !== null ? $val->format('d.m.Y') : $val;
+ }
+
+ protected function saveCreatedAt()
+ {
+ return new DateTime($this->createdAt);
+ }
+}
diff --git a/tests/app/FormLayer/NodeFormLayer.php b/tests/app/FormLayer/NodeFormLayer.php
new file mode 100644
index 0000000..ccb6f14
--- /dev/null
+++ b/tests/app/FormLayer/NodeFormLayer.php
@@ -0,0 +1,30 @@
+createdAt = $val !== null ? $val->format('d.m.Y') : $val;
+ }
+
+ protected function saveCreatedAt()
+ {
+ return new DateTime($this->createdAt);
+ }
+}
diff --git a/tests/app/Resources/config/doctrine/Node.orm.xml b/tests/app/Resources/config/doctrine/Node.orm.xml
new file mode 100644
index 0000000..8f9eedb
--- /dev/null
+++ b/tests/app/Resources/config/doctrine/Node.orm.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/app/config/config_test.yml b/tests/app/config/config_test.yml
new file mode 100644
index 0000000..0e13933
--- /dev/null
+++ b/tests/app/config/config_test.yml
@@ -0,0 +1,21 @@
+framework:
+ trusted_hosts: ~
+ secret: "test"
+ csrf_protection: false
+ test: ~
+ router:
+ resource: "%kernel.project_dir%/tests/app/config/routing.yml"
+maker:
+ root_namespace: Pfilsx\FormLayer\Tests\app
+doctrine:
+ dbal:
+ driver: 'pdo_sqlite'
+ memory: true
+ orm:
+ entity_managers:
+ default:
+ mappings:
+ Pfilsx\FormLayer\Tests\app\Entity\Node:
+ type: xml
+ dir: "%kernel.project_dir%/tests/app/Resources/config/doctrine"
+ prefix: Pfilsx\FormLayer\Tests\app\Entity
diff --git a/tests/app/config/routing.yml b/tests/app/config/routing.yml
new file mode 100644
index 0000000..01f3202
--- /dev/null
+++ b/tests/app/config/routing.yml
@@ -0,0 +1,6 @@
+test_prefix_show:
+ path: '/{id}/show'
+test_prefix_edit:
+ path: '/{id}/edit'
+test_prefix_delete:
+ path: '/{id}/delete'