This commit is contained in:
SinuS von SifriduS 2025-11-13 10:47:05 +01:00
commit eb334fec94
26 changed files with 1039 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
/bin/
/vendor/
/composer.phar
/composer.lock
/.phpunit.cache/
phpcs.cache
# eclipse
/.project
/.settings
/.buildpath

82
README.md Normal file
View File

@ -0,0 +1,82 @@
# Sasedev - Hidden Entity Type Bundle
Hidden entity type for Symfony forms.
[![paypal](https://img.shields.io/badge/Donate-Paypal-blue.svg)](http://paypal.me/Sasedev)
## What is it?
This is a Symfony form type that allows you to add an entity in your form that would be displayed as a hidden input.
## Installation
### Step 1: Download HiddenEntityTypeBundle using composer
```bash
$ composer require sasedev/hidden-entity-type-bundle
```
Composer will install the bundle to your project's vendor directory.
### Step 2: Enable the bundle
Enable the bundle in the config if flex it did´nt do it for you:
```php
<?php
// config/bundles.php
return [
// ...
Sasedev\HiddenEntityTypeBundle\SasedevHiddenEntityTypeBundle::class => ['all' => true],
// ...
];
```
## Usage
### Simple usage:
You can use the type in your forms just like this:
```php
<?php
use Sasedev\HiddenEntityTypeBundle\Form\Type\HiddenEntityType;
// ...
$builder->add('entity', HiddenEntityType::class, array(
'class' => YourBundleEntity::class
));
```
You can also use the `HiddenDocumentType::class` type:
```php
<?php
use Sasedev\HiddenEntityTypeBundle\Form\Type\HiddenDocumentType;
// ...
$builder->add('document', HiddenDocumentType::class, array(
'class' => YourBundleDocument::class
));
```
There is only one required option "class". You must specify entity class in Symfony format that you want to be used in your form.
### Advanced usage:
You can use the `HiddenEntityType` or `HiddenDocumentType` type in your forms this way:
```php
<?php
// ...
$builder->add('entity', HiddenEntityType::class, array(
'class' => YourBundleEntity::class, // required
'property' => 'entity_id', // Mapped property name (default is 'id'), not required
'multiple' => false, // support for an array of entities, not required
'data' => $entity, // Field value by default, not required
'invalid_message' => 'The entity does not exist.', // Message that would be shown if no entity found, not required
));
```
## Reporting an issue or a feature request
Feel free to report any issues. If you have an idea to make it better go ahead and modify and submit pull requests.
### Copy
This is a modified copy of source from Shapecode (https://github.com/shapecode/hidden-entity-type-bundle) that support sf>=7.3.
### Original
The orginal source is from Glifery (https://github.com/Glifery/EntityHiddenTypeBundle) but seems not to be supported anymore.

View File

@ -0,0 +1,20 @@
{
"symbol-whitelist": [
"null",
"true",
"false",
"static",
"self",
"parent",
"array",
"string",
"int",
"float",
"bool",
"iterable",
"callable",
"mixed",
"void",
"object"
]
}

9
composer-unused.php Normal file
View File

@ -0,0 +1,9 @@
<?php
declare(strict_types = 1);
use ComposerUnused\ComposerUnused\Configuration\Configuration;
use ComposerUnused\ComposerUnused\Configuration\NamedFilter;
return static function (Configuration $config): Configuration {
return $config->addNamedFilter(NamedFilter::fromString('symfony/framework-bundle'))->addNamedFilter(NamedFilter::fromString('symfony/yaml'));
};

102
composer.json Normal file
View File

@ -0,0 +1,102 @@
{
"name": "sasedev/hidden-entity-type-bundle",
"type": "symfony-bundle",
"description": "Hidden field for Symfony entities",
"keywords": [
"entity",
"hidden",
"form",
"type",
"shapecode",
"sasedev",
"symfony"
],
"homepage": "https://git.sasedev.com/Sasedev/hidden-entity-type-bundle",
"license": "MIT",
"authors": [
{
"name": "Salah Abdelkadeur Seifeddine",
"homepage": "https://sasedev.com",
"email": "sinus@sasedev.net"
},
{
"name": "Nikita Loges",
"homepage": "https://loges.one",
"email": "dev@loges.one"
},
{
"name": "Glifery",
"email": "glifery@gmail.com"
}
],
"autoload": {
"psr-4": {
"": "src"
}
},
"autoload-dev": {
"psr-4": {
"": "tests"
}
},
"require": {
"php": ">=8.2",
"symfony/framework-bundle": "^7.3.6",
"symfony/config": "^7.3.6",
"symfony/http-kernel": "^7.3.7",
"symfony/dependency-injection": "^7.3.6",
"symfony/form": "^7.3.6",
"symfony/yaml": "^7.3.5",
"symfony/options-resolver": "^7.3.3",
"symfony/property-access": "^7.3.3",
"symfony/property-info": "^7.3.5",
"doctrine/persistence": "^4.1.1",
"webmozart/assert": "^1.12.1"
},
"require-dev": {
"icanhazstring/composer-unused": "^0.9.5",
"doctrine/coding-standard": "^14.0",
"squizlabs/php_codesniffer": "^4.0.1",
"phpstan/phpstan": "^2.1.32",
"phpstan/phpstan-deprecation-rules": "^2.0.3",
"phpstan/phpstan-phpunit": "^2.0.8",
"phpstan/phpstan-strict-rules": "^2.0.7",
"phpstan/phpstan-webmozart-assert": "^2.0",
"maglnet/composer-require-checker": "^4.18",
"phpstan/phpstan-symfony": "^2.0.8",
"phpunit/phpunit": "^12.4.3",
"symfony/var-dumper": "^7.3.5"
},
"scripts": {
"check": [
"@crc",
"@unused",
"@cs-check",
"@phpstan",
"@phpunit"
],
"phpstan": "phpstan analyse --ansi",
"phpstan-update-baseline": "phpstan analyse --ansi --generate-baseline phpstan-baseline.neon",
"crc": "vendor/bin/composer-require-checker --config-file=./composer-require-checker.json --ansi",
"phpunit": "phpunit --colors=always",
"cs-check": "phpcs -s",
"cs-fix": "phpcbf",
"unused": "vendor/bin/composer-unused"
},
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"bin-dir": "bin",
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"icanhazstring/composer-unused": true
},
"bump-after-update": true,
"sort-packages": true
}
}

9
docker-compose.yml Normal file
View File

@ -0,0 +1,9 @@
version: '3.6'
services:
app:
image: webdevops/php:8.3
volumes:
- ./:/app/
environment:
PHP_MEMORY_LIMIT: 4G
PHP_DATE_TIMEZONE: Europe/Zurich

34
phpcs.xml Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
<arg value="p" />
<arg value="s" />
<arg name="colors" />
<arg name="parallel" value="4" />
<arg name="cache" value="./phpcs.cache" />
<config name="php_version" value="80300" />
<file>src/</file>
<file>tests/</file>
<rule ref="./vendor/doctrine/coding-standard/lib/Doctrine/ruleset.xml">
<exclude name="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix" />
<exclude name="SlevomatCodingStandard.Classes.SuperfluousAbstractClassNaming.SuperfluousPrefix" />
<exclude name="SlevomatCodingStandard.Classes.SuperfluousExceptionNaming.SuperfluousSuffix" />
<exclude name="Squiz.Arrays.ArrayDeclaration.MultiLineNotAllowed" />
</rule>
<rule ref="Squiz.WhiteSpace.MemberVarSpacing">
<properties>
<property name="spacing" value="1" />
<property name="spacingBeforeFirst" value="0" />
</properties>
</rule>
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="160" />
</properties>
</rule>
</ruleset>

6
phpstan-baseline.neon Normal file
View File

@ -0,0 +1,6 @@
parameters:
ignoreErrors:
-
message: "#^Method Sasedev\\\\HiddenEntityTypeBundle\\\\Form\\\\DataTransformer\\\\Transformer\\:\\:getRepository\\(\\) return type with generic interface Doctrine\\\\Persistence\\\\ObjectRepository does not specify its types\\: T$#"
count: 1
path: src/Sasedev/HiddenEntityTypeBundle/Form/DataTransformer/Transformer.php

16
phpstan.neon Normal file
View File

@ -0,0 +1,16 @@
includes:
- vendor/phpstan/phpstan/conf/bleedingEdge.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-webmozart-assert/extension.neon
- vendor/phpstan/phpstan-symfony/extension.neon
- vendor/phpstan/phpstan-symfony/rules.neon
- phpstan-baseline.neon
parameters:
level: max
paths:
- src
- tests

14
phpunit.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" bootstrap="vendor/autoload.php" executionOrder="depends,defects" beStrictAboutOutputDuringTests="true" cacheDirectory=".phpunit.cache" requireCoverageMetadata="true" beStrictAboutCoverageMetadata="true">
<coverage />
<testsuites>
<testsuite name="default">
<directory suffix="Test.php">tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
</phpunit>

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class SasedevHiddenEntityTypeExtension extends Extension {
/**
*
* @inheritDoc
*/
public function load(array $configs, ContainerBuilder $container): void {
$locator = new FileLocator(__DIR__ . '/../Resources/config');
$loader = new Loader\YamlFileLoader($container, $locator);
$loader->load('form.yaml');
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Form\DataTransformer;
use LogicException;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Webmozart\Assert\Assert;
use function is_numeric;
use function is_string;
use function sprintf;
/**
*
* @template-extends Transformer<object, string>
*/
class ObjectToIdTransformer extends Transformer {
public function transform(mixed $value): mixed {
if ($value === null) {
return null;
}
Assert::isInstanceOf($value, $this->class);
$accessor = PropertyAccess::createPropertyAccessor();
$property = $this->getProperty();
if (! $accessor->isReadable($value, $property)) {
return null;
}
$valueObject = $accessor->getValue($value, $property);
if ($valueObject === null) {
return null;
}
if (! is_string($valueObject) && ! is_numeric($valueObject)) {
throw new LogicException('id hast to be string or integer', 1653564596059);
}
return (string) $valueObject;
}
public function reverseTransform(mixed $value): mixed {
if ($value === null) {
return null;
}
$repo = $this->getRepository();
$property = $this->getProperty();
$class = $this->getClass();
$result = $repo->findOneBy([$property => $value]);
if ($result === null) {
throw new TransformationFailedException(sprintf('Can\'t find entity of class "%s" with property "%s" = "%s".', $class, $property, $value), 1701526691297);
}
Assert::isInstanceOf($result, $this->class);
return $result;
}
}

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Form\DataTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Webmozart\Assert\Assert;
use function count;
use function explode;
use function implode;
use function sprintf;
/**
*
* @template-extends Transformer<object[], string>
*/
class ObjectsToIdTransformer extends Transformer {
public function transform(mixed $value): mixed {
if ($value === null) {
return null;
}
Assert::allIsInstanceOf($value, $this->class);
$accessor = PropertyAccess::createPropertyAccessor();
$property = $this->getProperty();
$valueIds = [];
foreach ($value as $e) {
if (! $accessor->isReadable($e, $property)) {
continue;
}
$valueIds[] = $accessor->getValue($e, $property);
}
return implode(',', $valueIds);
}
public function reverseTransform(mixed $value): mixed {
if ($value === null) {
return [];
}
$repo = $this->getRepository();
$property = $this->getProperty();
$class = $this->getClass();
$ids = explode(',', $value);
$results = $repo->findBy([$property => $ids]);
if (count($results) === 0) {
throw new TransformationFailedException(sprintf('Can\'t find entity of class "%s" with property "%s" = "%s".', $class, $property, $value), 1701526676576);
}
Assert::allIsInstanceOf($results, $this->class);
return $results;
}
}

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Form\DataTransformer;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectRepository;
use InvalidArgumentException;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use function class_exists;
use function in_array;
use function sprintf;
/**
*
* @template TKey
* @template T
* @template-implements DataTransformerInterface<TKey, T>
*/
abstract class Transformer implements DataTransformerInterface {
/**
*
* @param class-string $class
*/
public function __construct(protected readonly ManagerRegistry $registry, protected readonly string $class, protected readonly string $property = 'id') {
if (! class_exists($class)) {
throw new InvalidArgumentException(sprintf('Expected an existing class name. Got: "%s"', $class), 1701527124965);
}
$this->validate();
}
protected function getRepository(): ObjectRepository {
return $this->registry->getRepository($this->getClass());
}
/**
*
* @return class-string
*/
protected function getClass(): string {
return $this->class;
}
protected function getProperty(): string {
return $this->property;
}
protected function validate(): void {
$reflectionExtractor = new ReflectionExtractor();
$propertyInfo = new PropertyInfoExtractor([$reflectionExtractor]);
$properties = $propertyInfo->getProperties($this->class) ?? [];
if (! in_array($this->property, $properties, true)) {
throw new NoSuchPropertyException(sprintf('property %s is missing in class %s', $this->property, $this->class), 1701527107565);
}
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class HiddenDocumentType extends AbstractType {
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(['invalid_message' => 'The document does not exist.']);
}
public function getParent(): string {
return HiddenObjectType::class;
}
public function getBlockPrefix(): string {
return 'sasedev_hidden_document';
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class HiddenEntityType extends AbstractType {
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(['invalid_message' => 'The entity does not exist.']);
}
public function getParent(): string {
return HiddenObjectType::class;
}
public function getBlockPrefix(): string {
return 'sasedev_hidden_entity';
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Form\Type;
use Doctrine\Persistence\ManagerRegistry;
use Sasedev\HiddenEntityTypeBundle\Form\DataTransformer\ObjectsToIdTransformer;
use Sasedev\HiddenEntityTypeBundle\Form\DataTransformer\ObjectToIdTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use function assert;
use function is_string;
class HiddenObjectType extends AbstractType {
public function __construct(protected readonly ManagerRegistry $registry) {
}
/**
*
* @param
* array<string, mixed> $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void {
$transformerClassName = $options['multiple'] === true ? ObjectsToIdTransformer::class : ObjectToIdTransformer::class;
/**
*
* @phpstan-var class-string $class
*/
$class = $options['class'];
$property = $options['property'];
assert(is_string($property));
$transformer = new $transformerClassName($this->registry, $class, $property);
$builder->addModelTransformer($transformer);
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setRequired(['class']);
$resolver->setDefaults(['multiple' => false,'data_class' => null,'invalid_message' => 'The object does not exist.','property' => 'id']);
$resolver->setAllowedTypes('invalid_message', ['null','string']);
$resolver->setAllowedTypes('property', ['string']);
$resolver->setAllowedTypes('multiple', ['boolean']);
}
public function getParent(): string {
return HiddenType::class;
}
public function getBlockPrefix(): string {
return 'sasedev_hidden_object';
}
}

View File

@ -0,0 +1,7 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Sasedev\HiddenEntityTypeBundle\Form\Type\HiddenObjectType: ~

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class SasedevHiddenEntityTypeBundle extends Bundle {
}

View File

@ -0,0 +1,114 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Tests\Form\DataTransformer;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectRepository;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Sasedev\HiddenEntityTypeBundle\Form\DataTransformer\ObjectToIdTransformer;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestFormModel;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
#[CoversClass(ObjectToIdTransformer::class)]
class ObjectToIdTransformerTest extends TestCase {
public function testValidTransformation(): void {
$object = new TestObject();
$object->setName('test');
// mock any dependencies
$objectRepository = $this->createConfiguredMock(ObjectRepository::class, ['findOneBy' => $object,'findBy' => [$object]]);
$registry = $this->createConfiguredMock(ManagerRegistry::class, ['getRepository' => $objectRepository]);
$transformer = new ObjectToIdTransformer($registry, TestObject::class, 'name');
$transformed = $transformer->transform($object);
$reversed = $transformer->reverseTransform('test');
self::assertEquals('test', $transformed);
self::assertEquals($object, $reversed);
}
public function testInvalidValidTransformation(): void {
$object = new TestObject();
$object->setName('test');
// mock any dependencies
$objectRepository = $this->createConfiguredMock(ObjectRepository::class, ['findOneBy' => null,'findBy' => [null]]);
$registry = $this->createConfiguredMock(ManagerRegistry::class, ['getRepository' => $objectRepository]);
$transformer = new ObjectToIdTransformer($registry, TestObject::class, 'name');
$this->expectException(TransformationFailedException::class);
// phpcs:disable Generic.Files.LineLength.TooLong
$this->expectExceptionMessage('Can\'t find entity of class "Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject" with property "name" = "test".');
// phpcs:enable Generic.Files.LineLength.TooLong
$transformed = $transformer->transform($object);
$reversed = $transformer->reverseTransform('test');
self::assertEquals('test', $transformed);
self::assertEquals(null, $reversed);
}
public function testInvalidProperty(): void {
$object = new TestObject();
$object->setName('test');
// mock any dependencies
$objectRepository = $this->createConfiguredMock(ObjectRepository::class, ['findOneBy' => null,'findBy' => [null]]);
$registry = $this->createConfiguredMock(ManagerRegistry::class, ['getRepository' => $objectRepository]);
$this->expectException(NoSuchPropertyException::class);
$this->expectExceptionMessage('property id is missing in class Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject');
new ObjectToIdTransformer($registry, TestObject::class, 'id');
}
public function testInvalidObject(): void {
$object = new TestFormModel();
$registry = $this->createMock(ManagerRegistry::class);
$transformer = new ObjectToIdTransformer($registry, TestObject::class, 'name');
$this->expectException(InvalidArgumentException::class);
// phpcs:disable Generic.Files.LineLength.TooLong
$this->expectExceptionMessage('Expected an instance of Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject. Got: Sasedev\HiddenEntityTypeBundle\Tests\Model\TestFormModel');
// phpcs:enable Generic.Files.LineLength.TooLong
$transformer->transform($object);
}
public function testInvalidArray(): void {
$object = new TestFormModel();
$registry = $this->createMock(ManagerRegistry::class);
$transformer = new ObjectToIdTransformer($registry, TestObject::class, 'name');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected an instance of Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject. Got: array');
// @phpstan-ignore-next-line
$transformer->transform([$object]);
}
public function testInvalidClass(): void {
$registry = $this->createMock(ManagerRegistry::class);
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected an existing class name. Got: "FakeClass"');
// @phpstan-ignore-next-line
new ObjectToIdTransformer($registry, 'FakeClass', 'name');
}
}

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Tests\Form\DataTransformer;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectRepository;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Sasedev\HiddenEntityTypeBundle\Form\DataTransformer\ObjectsToIdTransformer;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestFormModel;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
#[CoversClass(ObjectsToIdTransformer::class)]
class ObjectsToIdTransformerTest extends TestCase {
public function testValidTransformation(): void {
$object = new TestObject();
$object->setName('test');
// mock any dependencies
$objectRepository = $this->createConfiguredMock(ObjectRepository::class, ['findBy' => [$object]]);
$registry = $this->createConfiguredMock(ManagerRegistry::class, ['getRepository' => $objectRepository]);
$transformer = new ObjectsToIdTransformer($registry, TestObject::class, 'name');
$transformed = $transformer->transform([$object]);
$reversed = $transformer->reverseTransform('test');
self::assertEquals('test', $transformed);
self::assertEquals([$object], $reversed);
}
public function testInvalidValidTransformation(): void {
$object = new TestObject();
$object->setName('test');
// mock any dependencies
$objectRepository = $this->createConfiguredMock(ObjectRepository::class, ['findBy' => []]);
$registry = $this->createConfiguredMock(ManagerRegistry::class, ['getRepository' => $objectRepository]);
$transformer = new ObjectsToIdTransformer($registry, TestObject::class, 'name');
$this->expectException(TransformationFailedException::class);
// phpcs:disable Generic.Files.LineLength.TooLong
$this->expectExceptionMessage('Can\'t find entity of class "Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject" with property "name" = "test".');
// phpcs:enable Generic.Files.LineLength.TooLong
$transformed = $transformer->transform([$object]);
$reversed = $transformer->reverseTransform('test');
self::assertEquals('test', $transformed);
self::assertEquals(null, $reversed);
}
public function testInvalidProperty(): void {
$registry = $this->createMock(ManagerRegistry::class);
$this->expectException(NoSuchPropertyException::class);
$this->expectExceptionMessage('property id is missing in class Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject');
new ObjectsToIdTransformer($registry, TestObject::class, 'id');
}
public function testInvalidObject(): void {
$object = new TestFormModel();
$registry = $this->createMock(ManagerRegistry::class);
$transformer = new ObjectsToIdTransformer($registry, TestObject::class, 'name');
$this->expectException(InvalidArgumentException::class);
// phpcs:disable Generic.Files.LineLength.TooLong
$this->expectExceptionMessage('Expected an instance of Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject. Got: Sasedev\HiddenEntityTypeBundle\Tests\Model\TestFormModel');
// phpcs:enable Generic.Files.LineLength.TooLong
$transformer->transform([$object]);
}
public function testInvalidArray(): void {
$object = new TestFormModel();
$registry = $this->createMock(ManagerRegistry::class);
$transformer = new ObjectsToIdTransformer($registry, TestObject::class, 'name');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Expected an iterable. Got: Sasedev\HiddenEntityTypeBundle\Tests\Model\TestFormModel');
// @phpstan-ignore-next-line
$transformer->transform($object);
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Tests\Form\Type;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectRepository;
use PHPUnit\Framework\Attributes\CoversClass;
use Sasedev\HiddenEntityTypeBundle\Form\Type\HiddenObjectType;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestFormModel;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use function array_keys;
#[CoversClass(HiddenObjectType::class)]
class HiddenObjectTypeFailedTest extends TypeTestCase {
private ManagerRegistry $registry;
protected function setUp(): void {
// mock any dependencies
$objectRepository = $this->createConfiguredMock(ObjectRepository::class, ['findOneBy' => null,'findBy' => []]);
$this->registry = $this->createConfiguredMock(ManagerRegistry::class, ['getRepository' => $objectRepository]);
parent::setUp();
}
/**
*
* @return list<FormExtensionInterface>
*/
protected function getExtensions(): array {
// create a type instance with the mocked dependencies
$type = new HiddenObjectType($this->registry);
return [ // register the type instances with the PreloadedExtension
new PreloadedExtension([$type], [])];
}
public function testSubmitValidData(): void {
$formData = ['object' => 'test'];
$data = new TestFormModel();
$form = $this->factory->create(TestFormType::class, $data);
$form->submit($formData);
self::assertTrue($form->isSynchronized());
self::assertEquals(null, $data->getObject());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
self::assertArrayHasKey($key, $children);
}
}
}

View File

@ -0,0 +1,71 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Tests\Form\Type;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectRepository;
use PHPUnit\Framework\Attributes\CoversClass;
use Sasedev\HiddenEntityTypeBundle\Form\Type\HiddenObjectType;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestFormModel;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use function array_keys;
use function assert;
#[CoversClass(HiddenObjectType::class)]
class HiddenObjectTypeTest extends TypeTestCase {
private ManagerRegistry $registry;
private TestObject $testObject;
protected function setUp(): void {
$object = new TestObject();
$object->setName('test');
// mock any dependencies
$objectRepository = $this->createConfiguredMock(ObjectRepository::class, ['findOneBy' => $object,'findBy' => [$object]]);
$this->registry = $this->createConfiguredMock(ManagerRegistry::class, ['getRepository' => $objectRepository]);
$this->testObject = $object;
parent::setUp();
}
/**
*
* @return list<FormExtensionInterface>
*/
protected function getExtensions(): array {
// create a type instance with the mocked dependencies
$type = new HiddenObjectType($this->registry);
return [ // register the type instances with the PreloadedExtension
new PreloadedExtension([$type], [])];
}
public function testSubmitValidData(): void {
$formData = ['object' => 'test'];
$data = new TestFormModel();
$form = $this->factory->create(TestFormType::class, $data);
$form->submit($formData);
$testObject = $data->getObject();
assert($testObject instanceof TestObject);
self::assertTrue($form->isSynchronized());
self::assertEquals($this->testObject->getName(), $testObject->getName());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
self::assertArrayHasKey($key, $children);
}
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Tests\Form\Type;
use Sasedev\HiddenEntityTypeBundle\Form\Type\HiddenObjectType;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestFormModel;
use Sasedev\HiddenEntityTypeBundle\Tests\Model\TestObject;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TestFormType extends AbstractType {
/**
*
* @inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options): void {
$builder->add('object', HiddenObjectType::class, ['class' => TestObject::class,'property' => 'name']);
}
public function configureOptions(OptionsResolver $resolver): void {
$resolver->setDefaults(['data_class' => TestFormModel::class]);
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Tests\Model;
class TestFormModel {
private TestObject|null $object = null;
public function getObject(): TestObject|null {
return $this->object;
}
public function setObject(TestObject|null $object): void {
$this->object = $object;
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types = 1);
namespace Sasedev\HiddenEntityTypeBundle\Tests\Model;
class TestObject {
protected string|null $name = null;
public function getName(): string|null {
return $this->name;
}
public function setName(string|null $name): void {
$this->name = $name;
}
}