Skip to content

Commit

Permalink
Manage unmapped fields (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
webda2l authored Jun 18, 2020
1 parent dc060c3 commit ea50b7c
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 24 deletions.
28 changes: 20 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
--tmpfs /var/tmp:exec
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Install Composer
run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet
- name: Cache Composer dependencies
Expand All @@ -32,8 +32,12 @@ jobs:
composer-
- name: Validate Composer
run: composer validate
- name: Install xsl PHP extension
run: |
apk add $PHPIZE_DEPS libxslt-dev
docker-php-ext-install xsl
- name: Install highest dependencies with Composer
run: composer update --no-progress --no-suggest --ignore-platform-reqs --ansi
run: composer update --no-progress --no-suggest --ansi
- name: Disable PHP memory limit
run: echo 'memory_limit=-1' >> /usr/local/etc/php/php.ini
- name: Analyze
Expand All @@ -59,7 +63,7 @@ jobs:
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Install Composer
run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet
- name: Cache Composer dependencies
Expand All @@ -70,12 +74,16 @@ jobs:
restore-keys: |
composer-php${{ matrix.php }}-${{ matrix.dependencies }}-
composer-
- name: Install xsl PHP extension
run: |
apk add $PHPIZE_DEPS libxslt-dev
docker-php-ext-install xsl
- name: Install lowest dependencies with Composer
if: matrix.dependencies == 'lowest'
run: composer update --no-progress --no-suggest --prefer-stable --prefer-lowest --ignore-platform-reqs --ansi
run: composer update --no-progress --no-suggest --prefer-stable --prefer-lowest --ansi
- name: Install highest dependencies with Composer
if: matrix.dependencies == 'highest'
run: composer update --no-progress --no-suggest --ignore-platform-reqs --ansi
run: composer update --no-progress --no-suggest --ansi
- name: Run tests with PHPUnit
run: vendor/bin/simple-phpunit --colors=always

Expand All @@ -93,7 +101,7 @@ jobs:
- '7.4'
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Install pcov PHP extension
run: |
apk add $PHPIZE_DEPS
Expand All @@ -109,11 +117,15 @@ jobs:
restore-keys: |
composer-php${{ matrix.php }}-highest-
composer-
- name: Install xsl PHP extension
run: |
apk add $PHPIZE_DEPS libxslt-dev
docker-php-ext-install xsl
- name: Install highest dependencies with Composer
run: composer update --no-progress --no-suggest --ignore-platform-reqs --ansi
run: composer update --no-progress --no-suggest --ansi
- name: Run coverage with PHPUnit
run: vendor/bin/simple-phpunit --coverage-clover ./coverage.xml --colors=always
- name: Send code coverage report to Codecov.io
uses: codecov/[email protected]
uses: codecov/[email protected] # 1.0.4+ uncompatible alpine :/
with:
token: ${{ secrets.CODECOV_TOKEN }}
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
}
],
"require": {
"php": "^7.1.3",
"php": "^7.2",
"symfony/config": "^3.4.30|^4.3|^5.0",
"symfony/dependency-injection": "^3.4.30|^4.3|^5.0",
"symfony/doctrine-bridge": "^3.4.30|^4.3|^5.0",
"symfony/form": "^3.4.30|^4.3|^5.0",
"symfony/http-kernel": "^3.4.30|^4.3|^5.0"
"symfony/http-kernel": "^3.4.30|^4.3|^5.0",
"doctrine/persistence": "^1.3.7"
},
"require-dev": {
"doctrine/orm": "^2.7",
Expand Down
55 changes: 43 additions & 12 deletions src/Form/Manipulator/DoctrineORMManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
namespace A2lix\AutoFormBundle\Form\Manipulator;

use A2lix\AutoFormBundle\ObjectInfo\DoctrineORMInfo;
use Doctrine\Common\Util\ClassUtils;
use Symfony\Component\Form\FormInterface;

class DoctrineORMManipulator implements FormManipulatorInterface
Expand All @@ -37,25 +36,26 @@ public function getFieldsConfig(FormInterface $form): array

// Filtering to remove excludedFields
$objectFieldsConfig = $this->doctrineORMInfo->getFieldsConfig($class);
$validObjectFieldsConfig = $this->filteringValidFields($objectFieldsConfig, $formOptions['excluded_fields']);
$validObjectFieldsConfig = $this->filteringValidObjectFields($objectFieldsConfig, $formOptions['excluded_fields']);

if (empty($formOptions['fields'])) {
return $validObjectFieldsConfig;
}

// Check unknows fields
$unknowsFields = array_diff(array_keys($formOptions['fields']), array_keys($validObjectFieldsConfig));
if (\count($unknowsFields) > 0) {
throw new \RuntimeException(sprintf("Field(s) '%s' doesn't exist in %s", implode(', ', $unknowsFields), $class));
}
// Check correctness of remaining fields
$unmappedFieldsConfig = $this->filteringValidRemainingFields($validObjectFieldsConfig, $formOptions['fields'], $class);

foreach ($formOptions['fields'] as $formFieldName => $formFieldConfig) {
if (isset($unmappedFieldsConfig[$formFieldName])) {
continue;
}

if (null === $formFieldConfig) {
continue;
}

// If display undesired, remove
if (isset($formFieldConfig['display']) && (false === $formFieldConfig['display'])) {
if (false === ($formFieldConfig['display'] ?? true)) {
unset($validObjectFieldsConfig[$formFieldName]);
continue;
}
Expand All @@ -64,14 +64,18 @@ public function getFieldsConfig(FormInterface $form): array
$validObjectFieldsConfig[$formFieldName] = $formFieldConfig + $validObjectFieldsConfig[$formFieldName];
}

return $validObjectFieldsConfig;
return $validObjectFieldsConfig + $unmappedFieldsConfig;
}

private function getDataClass(FormInterface $form): string
{
// Simple case, data_class from current form
// Simple case, data_class from current form (with ORM Proxy management)
if (null !== $dataClass = $form->getConfig()->getDataClass()) {
return ClassUtils::getRealClass($dataClass);
if (false === $pos = strrpos($dataClass, '\\__CG__\\')) {
return $dataClass;
}

return substr($dataClass, $pos + 8);
}

// Advanced case, loop parent form to get closest fill data_class
Expand All @@ -87,7 +91,7 @@ private function getDataClass(FormInterface $form): string
throw new \RuntimeException('Unable to get dataClass');
}

private function filteringValidFields(array $objectFieldsConfig, array $formExcludedFields): array
private function filteringValidObjectFields(array $objectFieldsConfig, array $formExcludedFields): array
{
$excludedFields = array_merge($this->globalExcludedFields, $formExcludedFields);

Expand All @@ -102,4 +106,31 @@ private function filteringValidFields(array $objectFieldsConfig, array $formExcl

return $validFields;
}

private function filteringValidRemainingFields(array $validObjectFieldsConfig, array $formFields, string $class): array
{
$unmappedFieldsConfig = [];

$validObjectFieldsKeys = array_keys($validObjectFieldsConfig);
$unknowsFields = [];

foreach ($formFields as $fieldName => $fieldConfig) {
if (\in_array($fieldName, $validObjectFieldsKeys, true)) {
continue;
}

if (false === ($fieldConfig['mapped'] ?? true)) {
$unmappedFieldsConfig[$fieldName] = $fieldConfig;
continue;
}

$unknowsFields[] = $fieldName;
}

if (\count($unknowsFields) > 0) {
throw new \RuntimeException(sprintf("Field(s) '%s' doesn't exist in %s", implode(', ', $unknowsFields), $class));
}

return $unmappedFieldsConfig;
}
}
4 changes: 2 additions & 2 deletions src/ObjectInfo/DoctrineORMInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
namespace A2lix\AutoFormBundle\ObjectInfo;

use A2lix\AutoFormBundle\Form\Type\AutoFormType;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\ClassMetadataFactory;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class DoctrineORMInfo
Expand Down
137 changes: 137 additions & 0 deletions tests/Form/Type/AutoFormTypeAdvancedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

declare(strict_types=1);

/*
* This file is part of the AutoFormBundle package.
*
* (c) David ALLIX <http://a2lix.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace A2lix\AutoFormBundle\Tests\Form\Type;

use A2lix\AutoFormBundle\Form\Type\AutoFormType;
use A2lix\AutoFormBundle\Tests\Fixtures\Entity\Media;
use A2lix\AutoFormBundle\Tests\Fixtures\Entity\Product;
use A2lix\AutoFormBundle\Tests\Form\TypeTestCase;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\PreloadedExtension;

/**
* @internal
*/
final class AutoFormTypeAdvancedTest extends TypeTestCase
{
public function testCreationFormWithOverriddenFieldsLabel(): Product
{
$form = $this->factory->createBuilder(AutoFormType::class, new Product(), [
'fields' => [
'url' => [
'label' => 'URL/URI',
],
],
])
->add('create', SubmitType::class)
->getForm()
;

$media1 = new Media();
$media1->setUrl('http://example.org/media1')
->setDescription('media1 desc')
;
$media2 = new Media();
$media2->setUrl('http://example.org/media2')
->setDescription('media2 desc')
;

$product = new Product();
$product->setUrl('a2lix.fr')
->addMedia($media1)
->addMedia($media2)
;

$formData = [
'url' => 'a2lix.fr',
'medias' => [
[
'url' => 'http://example.org/media1',
'description' => 'media1 desc',
],
[
'url' => 'http://example.org/media2',
'description' => 'media2 desc',
],
],
];

$form->submit($formData);
static::assertTrue($form->isSynchronized());
static::assertEquals($product, $form->getData());
static::assertEquals('URL/URI', $form->get('url')->getConfig()->getOptions()['label']);

return $product;
}

public function testCreationFormWithOverriddenFieldsMappedFalse(): Product
{
$form = $this->factory->createBuilder(AutoFormType::class, new Product(), [
'fields' => [
'color' => [
'mapped' => false,
],
],
])
->add('create', SubmitType::class)
->getForm()
;

$media1 = new Media();
$media1->setUrl('http://example.org/media1')
->setDescription('media1 desc')
;
$media2 = new Media();
$media2->setUrl('http://example.org/media2')
->setDescription('media2 desc')
;

$product = new Product();
$product->setUrl('a2lix.fr')
->addMedia($media1)
->addMedia($media2)
;

$formData = [
'url' => 'a2lix.fr',
'color' => 'blue',
'medias' => [
[
'url' => 'http://example.org/media1',
'description' => 'media1 desc',
],
[
'url' => 'http://example.org/media2',
'description' => 'media2 desc',
],
],
];

$form->submit($formData);
static::assertTrue($form->isSynchronized());
static::assertEquals($product, $form->getData());
static::assertEquals('blue', $form->get('color')->getData());

return $product;
}

protected function getExtensions(): array
{
$autoFormType = $this->getConfiguredAutoFormType();

return [new PreloadedExtension([
$autoFormType,
], [])];
}
}

0 comments on commit ea50b7c

Please sign in to comment.