init
This commit is contained in:
commit
eb334fec94
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/bin/
|
||||
/vendor/
|
||||
/composer.phar
|
||||
/composer.lock
|
||||
/.phpunit.cache/
|
||||
phpcs.cache
|
||||
# eclipse
|
||||
/.project
|
||||
/.settings
|
||||
/.buildpath
|
||||
82
README.md
Normal file
82
README.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Sasedev - Hidden Entity Type Bundle
|
||||
|
||||
Hidden entity type for Symfony forms.
|
||||
|
||||
[](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.
|
||||
20
composer-require-checker.json
Normal file
20
composer-require-checker.json
Normal 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
9
composer-unused.php
Normal 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
102
composer.json
Normal 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
9
docker-compose.yml
Normal 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
34
phpcs.xml
Normal 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
6
phpstan-baseline.neon
Normal 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
16
phpstan.neon
Normal 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
14
phpunit.xml
Normal 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>
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
Sasedev\HiddenEntityTypeBundle\Form\Type\HiddenObjectType: ~
|
||||
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace Sasedev\HiddenEntityTypeBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class SasedevHiddenEntityTypeBundle extends Bundle {
|
||||
}
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user