GithubHelp home page GithubHelp logo

limenius / liformbundle Goto Github PK

View Code? Open in Web Editor NEW
138.0 10.0 45.0 92 KB

Symfony Bundle to render Symfony Forms to JSON Schema

License: MIT License

PHP 100.00%
forms json-schema reactjs symfony symfony-bundle

liformbundle's Introduction

LiformBundle

Bundle that integrates Liform into Symfony. Liform is a library to serialize Symfony Forms into JSON schema. For use with liform-react or json-editor, or any other form generator based on json-schema.

It is very annoying to maintain Symfony forms that match forms in a client technology, such as JavaScript. It is also annoying to maintain a documentation of such forms. And it's error prone, too.

LiformBundle generates a JSON schema representation, that serves as documentation and can be used to validate your data and, if you want, to generate forms using a generator.

Installation

First and foremost, note that you have a complete example with React, Webpack and Symfony Standard Edition at Limenius/symfony-react-sandbox ready for you, which includes an example implementation of this bundle.

Feel free to clone it, run it, experiment, and copy the pieces you need to your project. Because this bundle focuses mainly on the frontend side of things, you are expected to have a compatible frontend setup.

Step 1: Download the Bundle

Open a console, navigate to your project directory and execute the following command to download the latest stable version of this bundle:

$ composer require limenius/liform-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:

// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...

            new Limenius\LiformBundle\LimeniusLiformBundle(),
        );

        // ...
    }

    // ...
}

Usage

Serializing a form into JSON Schema:

        $form = $this->createForm(CarType::class, $car, ['csrf_protection' => false]);
        $schema = json_encode($this->get('liform')->transform($form));

And $schema will contain a JSON Schema representation such as:

{  
   "title":null,
   "properties":{  
      "name":{  
         "type":"string",
         "title":"Name",
         "propertyOrder":1
      },
      "color":{  
         "type":"string",
         "title":"Color",
         "attr":{  
            "placeholder":"444444"
         },
         "default":"444444",
         "description":"3 hexadecimal digits",
         "propertyOrder":2
      },
      "drivers":{  
         "type":"array",
         "title":"hola",
         "items":{  
            "title":"Drivers",
            "properties":{  
               "firstName":{  
                  "type":"string",
                  "propertyOrder":1
               },
               "familyName":{  
                  "type":"string",
                  "propertyOrder":2
               }
            },
            "required":[  
               "firstName",
               "familyName"
            ],
            "type":"object"
         },
         "propertyOrder":3
      }
   },
   "required":[  
      "name",
      "drivers"
   ]
}

Information extracted to JSON-schema

The goal of Liform is to extract as much data as possible from the form in order to have a complete representation with validation and UI hints in the schema. The options currently supported are.

Check out the Liform documentation for more details.

Using your own transformers

Liform works by recursively inspecting the form, finding (resolving) the right transformer for every child and using that transformer to build the corresponding slice of the json-schema. So, if you want to modify the way a particular form type is transformed, you can add a transformer and configure it to to be applied for all children with a particular block_prefix.
To achieve this, you should create a new service definition and add the liform.transformer tag. You need to specify for which form-types your transformer will be applied by setting the form_type property of the tag to the corresponding block_prefix.

In the following example we are reusing the StringTransformer class. By specifying the widget property of the tag we can scope the transformer to only work for types with that particular widget.

services:
    app.liform.file_type.transformer:
        class: "%liform.transformer.string.class%"
        parent: Limenius\Liform\Transformer\AbstractTransformer
        tags:
            - { name: liform.transformer, form_type: file, widget: file_widget }

You can of course use your very own Transformer class, just make sure to implement the required Limenius\Liform\Transformer\TransformerInterface when you do.

Extending the default behaviour

In addition to adding your own transformers for customizing the serialization of a specific form-type Liform allows you to add extensions to customize the default behaviour of all types.
In the following example we use an Extension to add a submit_url property to the schema representing the form's action parameter.

<?php

use Limenius\Liform\Transformer\ExtensionInterface;
use Symfony\Component\Form\FormInterface;

class FormDataExtension implements ExtensionInterface
{
    /**
     * @param FormInterface $form
     * @param array         $schema
     *
     * @return array
     */
    public function apply(FormInterface $form, array $schema)
    {
        if (!$form->isRoot()) {
            return $schema;
        }

        if (!$form->getConfig()->hasOption('action')) {
            return $schema;
        }

        $schema['submit_url'] = $form->getConfig()->getOption('action');

        return $schema;
    }
}

Make sure your Extension class implements the required Limenius\Liform\Transformer\ExtensionInterface. To register your extension; create a new service definition and add the liform.extension tag to it.

services:
    app.liform.form_data.extension:
        class: MyProject\Application\Liform\FormDataExtension
        tags:
            - { name: liform.extension }

Serializing initial values

This bundle registers a normalizer to serialize a FormView class into an array of initial values that match your json-schema. The following example shows you how to use this feature in a controller action:

$serializer = $this->get('serializer');
$initialValues = $serializer->normalize($form);

Serializing errors

This bundle registers a normalizer to serialize forms with errors into an array. This part was shamelessly taken from FOSRestBundle. Copy the following statements to use this feature:

$serializer = $this->get('serializer');
$errors = $serializer->normalize($form);

The format of the array containing the normalized form errors is compatible with the liform-react package.

License

This bundle was released under the MIT license. For the full copyright and license information, please view the LICENSE file that was distributed with this source code.

LICENSE.md

Acknowledgements

The technique for transforming forms using resolvers and reducers is inspired on Symfony Console Form

liformbundle's People

Contributors

mablae avatar nacmartin avatar phpwutz avatar sjopet avatar umpirsky avatar victoriaq avatar vutran avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

liformbundle's Issues

Allow injection via LiformInterface

Fetching the service via $this->get, as described in the README, has been removed in Symfony 6, as all services are private by default and the container and get() helper methods aren't available.

Instead, the documentation should reflect getting the service by injecting it in the contructor or the controller method, e..g

__construct(private Liform $liform)

Ideally, though, this would work with LiformInterface (the abstract instead of the concrete).

move to answear

https://github.com/answear/LiformBundle is the latest version of this bundle, and works with Symfony 6.

However, since it's a fork it does not allow for adding issues.

It seems like answear should become the maintainer of this bundle, since it's the most up to date.

Does not support full range of native FormTypes - Symfony 4.3.4

I'm uncertain whether the issue I'm experiencing is because my current version of Symfony 4.3.4 is unsupported by this bundle, or due to some specific implementation issue in our installation, or whether actually the bundle does not support all the different types included by Symfony.

use Limenius\Liform\Resolver;
use Limenius\Liform\Liform;
use Limenius\Liform\Liform\Transformer;
...
public function edit(Activity $activity, Liform $liform, Request $request)
    {
    // $builder->add('startDate', DateType::class, [
    $form = $this->createForm(ActivityFormType::class, $activity, []);
    $schema = $liform->transform($form);
...
}

The above pattern works as expected for TextType, and ChoiceType, but falls over for DateType with the error

"Could not find a transformer for any of these types (date, form) "

The documenation does not make it clear whether I should expect this functionality to exist. It suggests I will need to add custom work to include custom extension, however seems to imply that it should work out the box with a standard Symfony install.

I notice that DateType and EntityType do not seem to be in the list of available transformers here.

https://github.com/Limenius/LiformBundle/blob/master/Resources/config/transformers.xml

Am I correct then in my understanding that DateType which is a standard Symfony type is not supported?
https://symfony.com/doc/current/reference/forms/types/date.html

Edit
Also from here http://nacho-martin.com/serializing-symfony-forms-to-json-schema.html
This overview seems to imply dates are supported
->add('dueTo', Type\DateTimeType::class, [
Using the DateTimeType we get a similar error Could not find a transformer for any of these types (datetime)

Impossible to not have a title

Hi,

In a FormType, if we have 'label' => false, it's normalized by the form name instead of null (or a blank string if we want to follow the RFC).

In your code:

protected function addLabel(FormInterface $form, array $schema)
{
	$translationDomain = $form->getConfig()->getOption('translation_domain');
	if ($label = $form->getConfig()->getOption('label')) {
		$schema['title'] = $this->translator->trans($label, [], $translationDomain);
	} else {
		$schema['title'] = $this->translator->trans($form->getName(), [], $translationDomain);
	}

	return $schema;
}

should be changed by:

protected function addLabel(FormInterface $form, array $schema)
{
	$translationDomain = $form->getConfig()->getOption('translation_domain');
	if ($label = $form->getConfig()->getOption('label')) {
		$schema['title'] = $this->translator->trans($label, [], $translationDomain);
	} elseif (false === $form->getConfig()->getOption('label')) {
		$schema['title'] = '';
	} else {
		$schema['title'] = $this->translator->trans($form->getName(), [], $translationDomain);
	}

	return $schema;
}

DateTimeType widget single_text

   $builder
            ->add('date', DateTimeType::class, [
                'widget' => 'single_text',
            ])
        ;

result => Could not find a transformer for any of these types (datetime, form)

Actualy a bypass by using a simple text input and appy a transformer on it :

->addModelTransformer(new CallbackTransformer(
                function ($datetimeObj) {
                    if ($datetimeObj === null) {
                        return '';
                    }
                    return $datetimeObj->format('Y-m-d H:i:s');
                },
                function ($datetimeString) {
                    return \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $datetimeString, new \DateTimeZone('UTC'));
                }
            ))

DateType json response widget date

Hi,

I am a newer using Liform connect with angular-schema-form.

Also, maybe this is reported already, but I am receiving from json form which has a DataType field a key widget="date"

Anyway to pass this a render a datepicker (which I am doing) ?

Thanks in advance.

UndefinedMethodException $resolver->setDefaultTransformers();

Hello
Symfony 3.4 is telling me that the method setDefaultTransformers() is not defined.

I checked Limenius\Liform\Resolver and could not find that method.

This is how my controller looks like:

`use Limenius\Liform\Resolver;
 use Limenius\Liform\Liform;

 class AdController extends Controller
{
/**
 * @Route("/nouvelle-annonce-voiture-occasion", name="new_ad_page")
 */
public function showAction(Request $request)
{
	$resolver = new Resolver();
	$resolver->setDefaultTransformers();
	$liform = new Liform($resolver);

    $ad = new Ad();
    //$form = $this->createForm(AdType::class, $ad);
	$form = $this->createForm(AdType::class, $ad);
	$schema = json_encode($liform->transform($form));
			\Doctrine\Common\Util\Debug::dump($schema);`

Also, you may notice that I removed the csrf_protection => false option from createForm as it would give me the error below. Instead I placed that option on the Type class, as follows:

public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => Ad::class, 'csrf_protection' => false, )); }

This is the error: (if I leave the csrf_protection where you instruct in docs)

The option "csrf_protection" does not exist. Defined options are: "action", "allow_extra_fields", "attr", "auto_initialize", "block_name", "by_reference", "compound", "constraints", "data", "data_class", "disabled", "empty_data", "error_bubbling", "error_mapping", "extra_fields_message", "inherit_data", "invalid_message", "invalid_message_parameters", "label", "label_attr", "label_format", "liform", "mapped", "method", "post_max_size_message", "property_path", "required", "translation_domain", "trim", "upload_max_size_message", "validation_groups".

Thanks

widget instead of format

The bundle is working properly, but it returns widget instead of format.
"address": {
"type": "string",
"title": "Email address",
"widget": "email",
"propertyOrder": 2
},

Release please

Hey guys,

can we get a new release? The latest one is missing b46ba76 CSRF token fix.

Thanks!

Unable to install via composer due to conflicting dependencies

Environment: PHP 7.4 and Symfony 5.0.6 with Flex.

When running composer require limenius/liform-bundle ^0.16.0, the following error occurs:

Your requirements could not be resolved to an installable set of packages.       

  Problem 1
    - limenius/liform-bundle v0.16.0 requires limenius/liform ^0.16.0 -> satisfiable by limenius/liform[v0.16.0].
    - Installation request for limenius/liform-bundle ^0.16.0 -> satisfiable by limenius/liform-bundle[v0.16.0].
    - Conclusion: remove symfony/cache-contracts v2.0.1
    - Conclusion: don't install symfony/cache-contracts v2.0.1
    - limenius/liform v0.16.0 requires symfony/contracts ^2.1 -> satisfiable by symfony/contracts[v2.1.0, v2.1.1, v2.1.2, v2.1.3].
    - don't install symfony/contracts v2.1.0|don't install symfony/cache-contracts v2.0.1
    - don't install symfony/contracts v2.1.1|don't install symfony/cache-contracts v2.0.1
    - don't install symfony/contracts v2.1.2|don't install symfony/cache-contracts v2.0.1
    - don't install symfony/contracts v2.1.3|don't install symfony/cache-contracts v2.0.1
    - Installation request for symfony/cache-contracts (locked at v2.0.1) -> satisfiable by symfony/cache-contracts[v2.0.1].

Additional research (probably not so helpful) yielded this output:

$ composer why symfony/cache-contracts
symfony/cache  v5.0.6  requires  symfony/cache-contracts (^1.1.7|^2)

$ composer require symfony/contracts ^2.1.0
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Conclusion: don't install symfony/contracts v2.1.3
    - Conclusion: don't install symfony/contracts v2.1.2
    - Conclusion: don't install symfony/contracts v2.1.1
    - don't install symfony/http-client-contracts v2.0.1|don't install symfony/contracts v2.1.0     
    - don't install symfony/contracts v2.1.0|remove symfony/http-client-contracts v2.0.1
    - don't install symfony/contracts v2.1.0|don't install symfony/http-client-contracts v2.0.1     
    - Installation request for symfony/contracts ^2.1.0 -> satisfiable by symfony/contracts[v2.1.0, 
v2.1.1, v2.1.2, v2.1.3].
    - Installation request for symfony/http-client-contracts (locked at v2.0.1) -> satisfiable by symfony/http-client-contracts[v2.0.1].

I suppose this might be a version incompatibility with symfony/cache 5.0.6, but I'll be thankful for any help!

There is no "serializer" service

Docs says:

This bundle registers a normalizer to serialize forms with errors into an array.

$serializer = $this->get('serializer');
$initialValues = $serializer->normalize($form);

but there is no "serializer" service registered. I see in services.yml that there is InitialValuesNormalizer

<service id="liform.serializer.initial_values_normalizer" class="Limenius\Liform\Serializer\Normalizer\InitialValuesNormalizer" public="false">
  <tag name="serializer.normalizer" priority="-10" />
</service>

but it is public="false" so I can't access it.

I have eneded with this:

$normalizer = new \Limenius\Liform\Serializer\Normalizer\InitialValuesNormalizer();
$initialValues = $normalizer->normalize($form);

undefined method named "getBlockPrefix"

Got this error when I tried to generate a schema with liform :

CRITICAL - Uncaught PHP Exception Symfony\Component\Debug\Exception\UndefinedMethodException: "Attempted to call an undefined method named "getBlockPrefix" of class "Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeDataCollectorProxy"." at /home/admin/Documents/displayce/code/vendor/limenius/liform/src/Limenius/Liform/FormUtil.php line 39

Here my two code lines :
$editForm = $this->createForm(new EditType(), $entity, array( 'method' => 'PUT', 'csrf_protection' => false, ));
$schema = json_encode($this->get('liform')->transform($editForm));

I saw a bug related to that and it seems to be fixed with 2.8 version but of course my project is using 2.7 : https://github.com/symfony/symfony/issues/15760

Abstract transformer need to use translation domain for correct label translation and should be used for placeholder too

Thanks for this great usefull bundle, i just got a problem, you call translator "As It" without check for any translation domain who can be specified in symfony form builder, and look like you dont use translator at all for placeholder, as i'm going to have a base project who have to be able to maintain multi-language functionality i think you just need a little change to make translator work like a charm without put all in a messages.{lang}.yml


namespace Limenius\Liform\Transformer;


abstract class AbstractTransformer {

///...

protected function addLabel($form, $schema)
    {
        if ($label = $form->getConfig()->getOption('label')) {
            $schema['title'] = $this->translator->trans($label); // <----- should be ' .... '->trans($label, [], $TranslationDomainSpecifiedFromSymfonyFormBuildler )
        } else {
            $schema['title'] = $this->translator->trans($form->getName());
        }

        return $schema;
    }

//....

}

Edit : this is related to https://github.com/Limenius/Liform i'll make a PR to solve this issue

React Status Code ?

Hello,

I have a server_side rendering who work's fine, and I try to send specific status code (404, 503...) on certains routes, but I always get 200 and I don't know how to do that.

Could you help me ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.