Coder Social home page Coder Social logo

simplethingsformserializerbundle's Introduction

FormSerializerBundle

Bundle that helps solving the Serializer/Form component API missmatch. This missmatch leads to non-reusable code in controllers, bloating every application by reimplementing everything over and over again. Currently its nearly impossible to re-use REST-API calls and HTML/Form-based submits in the same controller action. Additionally all the current serializer components share a common flaw: They cannot deserialize (update) into existing object graphs. Updating object graphs is a problem the Form component already solves (perfectly!).

This bundle solves these issues by hooking into the Form Framework. It allows to implement serializers for objects, using form types. For deserializing it uses the exact same API as "usual" form requests.

The Form component is a very good serialization library, but up to now it only had one implementation: HTML Forms. This bundle adds support for hooking the Serializer Encoders into this process, XML and JSON supported by default.

New Form Options

Using this bundle you gain new form type options. The "form" field is overwritten to have the following additional configuration keys:

  • serialize_xml_name - Specifies the root xml name or the list entry xml name of elements, depending on its definition on a parent or child element. (Default: entry)
  • serialize_xml_value - If true, this field will be the xml value of the parent field. Useful if you have small embedded types that have some attributes and one value. (Default: false)
  • serialize_xml_attribute - If true, this field will be rendered as attribute on the parent in xml, not as an element. (Default: false)
  • serialize_xml_inline - If true, no collection wrapper element will be rendered for a collection of elements. If false, wrap all elements. (Default: true)
  • serialize_name - Custom name of the element in serialized form if it should deviate from the default naming strategy of turning camel-case into underscore. (Default: false)
  • serialize_only - If true the field will be removed from FormView and therefor only be present in the serialized data (json, xml)

Usage

This bundle defines a new service to serialize forms inside the Symfony DIC:

class UserController extends Controller
{
    public function showAction(User $user)
    {
        $serializer = $this->get('form_serializer');
        $xml = $serializer->serialize($user, new UserType(), 'xml');

        return new Response($xml, 200, array('Content-Type' => 'text/xml'));
    }
}

The API for the FormListener is documented on the FormSerializerInterface it implements:

interface FormSerializerInterface
{
    /**
     * Serialize a list of objects, where each element is serialized based on a
     * form type.
     *
     * @param array|Traversable $list
     * @param FormTypeInterface $type
     * @param string $format
     * @param string $xmlRootName
     *
     * @return string
     */
    public function serializeList($list, $type, $format, $xmlRootName = 'entries');

    /**
     * Serialize an object based on a form type, form builder or form instance.
     *
     * @param mixed $object
     * @param FormTypeInterface|FormBuilderInterface|FormInterface $typeBuilder
     * @param string $format
     *
     * @return string
     */
    public function serialize($object, $typeBuilder, $format);
}

The bundle also registers a Listener inside the form framework that binds XML and JSON requests onto a form. Just call $form->bind($request) as shown in the example below.

If you want to convert JMS Serializer based configuration to FormTypes you can use the command that is included:

php app/console simplethings:convert-jms-metadata "className"

Since JMS Serializer automatically builds metadata for every class, you can use this command to generate form types for any existing class for you.

Configuration

Default DIC (config.yml) configuration:

simple_things_form_serializer:
    include_root_in_json: false
    application_xml_root_name: ~
    naming_strategy: camel_case
    encoders:
        xml: true
        json: true

Dependency Injection tag named simple_things_form_serializer.encoder to add more encoders.

Example

Take a usual form, extended with some details about serialization:

namespace Acme\DemoBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('username', 'text')
            ->add('email', 'email')
            ->add('country', 'entity')
            ->add('addresses', 'collection', array('type' => 'address', 'serialize_xml_name' => 'address'))
            ->add('created', 'datetime', array('read_only' => true))
        ;
    }

    public function getName()
    {
        return 'user';
    }

    public function setDefaultOptions(OptionsResolverInterface $options)
    {
        $options->setDefaults(array(
            'data_class' => 'Acme\DemoBundle\Entity\User',
            'serialize_xml_name' => 'user',
        ));
    }
}

Using the serializer:

$serializer = $this->get('form_serializer');
$data       = $serializer->serialize($user, new UserType(), 'xml');

Produces:

<user>
    <username>beberlei</username>
    <email>[email protected]</email>
    <country>de</country>
    <addresses>
        <address>
            <street>Foostreet 1</street>
        </address>
    </addresses>
    <created>2012-07-10</created>
</user>

Or if you use JSON:

{
    "user": {
        "username": "beberlei",
        "email": "[email protected]",
        "country": "de",
        "addresses": [
            {"street": "Foostreet 1"}
        ],
        "created": "2012-07-10"
    }
}

Deserializing will look familiar:

class UserController extends Controller
{
    /**
     * @Method("POST")
     */
    public function editAction(Request $request)
    {
        $em = $this->get('doctrine.orm.default_entity_manager');

        $user = $em->find('Acme\DemoBundle\Entity\User', $request->get('id'));
        $form = $this->createForm(new UserType(), $user);

        $form->bind($request);

        if ( ! $form->isValid()) {
            return $this->renderFormFailure("MyBundle:User:edit.html.twig", $form, array('user' => $user));
        }

        // do some business logic here

        $em->flush();

        return $this->formRedirect($form, $this->generateUrl('user_show', array('id' => $user->getId()), 201);
    }

    /* either render the form errors as xml/json or the html form again based on " _format" */
    public function renderFormFailure($template, FormInterface $form, $parameters)
    {
    }

    /* redirect OR 201 created, based on the "_format" */
    public function formRedirect()
    {
    }
}

This looks almost like a out of the book form request. The only thing different is that we have to use the "renderFormView" and "formRedirect" methods to generate response objects.

  • renderFormView will decide based on the response format, what operation to perform.
    1. show a form, when format is html
    2. show a HTTP 405 error, when the passed form wasn't bound yet
    3. show a HTTP 412 pre-condition failed with the form errors serialized into xml or json.
  • formRedirect will decide based on the response format:
    1. to redirect to the given url if its html (or config option use_forwards = false)
    2. to forward to the route if its xml or json (and config option use_forwards = true)

simplethingsformserializerbundle's People

Contributors

adel-e avatar beberlei avatar ccapndave avatar davidbadura avatar henrikbjorn avatar lsmith77 avatar metabor avatar pborreli 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

simplethingsformserializerbundle's Issues

Link Relations

Allow every form to define link relations which are rendered.

JSON:

"links": {}

XML:

<atom:link .../>

serialize_collection_form Attribute Not Passed Options

In CollectionTypeExtension.php, the mockup child form that is created and inserted into the "serialize_collection_form" attribute of Collections is not created with the "options" option passed to the collection.

This is a problem when the child form requires a particular option, or changes its behavior based on a particular option. I modified line 26 so that 24-27 read

    $builder->setAttribute(
        'serialize_collection_form',
        $builder->getFormFactory()->create($options['type'], null, $options['options']) // CHANGED, added "options"
    );

Incompatibility with some LifeCycleEvents

When using Backbone.js, a common practice is to return a new model representation after it is processed by the update action (usually a PUT request). This is useful to update the client-side model with server-side generated data like updatedAt fields or other domain logic data.

However, Backbone.js does not work well with differential updates when saving models (PATCH support is funky) - it always sends the complete model object back to the server. Because of the way this is handled, any update action needs to return a complete form serialized back to the client after a successful save - and this means I need the updatedAt field on that form.

The problem is that the unserializeForm will now let the Form component set that value on the object, as if it was manually set. This breaks LifeCycleEvents that depend on this check to determine wether a new value should be set or not (see the TimestampableListener from Gedmo).

Any ideas or workarounds to solve this?

Bind a form to a serialized xml string instead of a request ?

Is it possible to bind a form to an xml string instead of a request ?

More generally, is it possible to restore an entity that has been serialized in a database (says, as a XML string using form_serializer).

Something like

$form->submit($entitySerializedAsXml);

Thanks in advance

Submit buttons are missing everywhere if SimpleThingsFormSerializerBundle is in AppKernel

I just installed the bundle (dev-master)

php composer.phar require simplethings/form-serializer-bundle dev-master

and added the bundle to AppKernel.php

new SimpleThings\FormSerializerBundle\SimpleThingsFormSerializerBundle(),

Then submit buttons on my forms disappear everywhere on my symfony2 app.
Any idea on what is happening ?

Symfony2 version: 2.6.4

Serializing all types to strings?

Hello,

I'm trying to use your bundle for our application REST API, and I'm serializing my objects to json format.

I need to keep parameter types when serializing, so that null values will be nulls after serialization, and integers remain integers. But I found that for some reason you convert all the json values to strings when calling getClientData() method in serializeForm(). If I change this to getNormData() - everything works just fine.

The question is - was that done intentionally? If yes, then why?

CSRF Handling

When CSRF protection is enabled funky form validation errors appear

Escaped JSON output

The output of renderForm is currently escaped. Is there any way to return a unescaped JSON string like FOSRest?

DateTime serialization

When serializing a DateTime object without the single_text form option, the serialized response is empty:

"{"name":"sample","created_at":{"date":{"year":"","month":"","day":""},"time":{"hour":"","minute":""}}}"

Same object with single_text:

"{"name":"sample","created_at":"2012-07-20T15:49:59Z"}"

Form children serialization

I'm currently implementing a Closure table tree model and my File object has a $parent property which points to itself. I need this in order to correctly render the File parent nameon the client-side.

To serialize this information, I need to add this field to the FileType form as a new FileType form type. This causes a stackoverflow.

class FileType extends AbstractType
{
      $builder
          ->add('name')
          ->add('parent', new FileType, array('serialize_only' => true))
      ;

}

Simplified example:

{
  category: "folder"
  name: "sample"
  parent: {
    category: "folder"
    name: "home"
}

Stacktrace:

( ! ) Fatal error: Maximum function nesting level of '300' reached, aborting! in vendor/symfony/symfony/src/Symfony/Component/OptionsResolver/Options.php on line 179
Call Stack
#   Time    Memory  Function    Location
1   0.0001  243736  {main}( )   ../app_dev.php:0
2   0.0045  1206520 Symfony\Component\HttpKernel\Kernel->handle( )  ../app_dev.php:28
3   0.1373  3378576 Symfony\Bundle\FrameworkBundle\HttpKernel->handle( )    ../bootstrap.php.cache:610
4   0.1374  3380328 Symfony\Component\HttpKernel\HttpKernel->handle( )  ../bootstrap.php.cache:1555
5   0.1374  3380392 Symfony\Component\HttpKernel\HttpKernel->handleRaw( )   ../bootstrap.php.cache:1383
6   0.3574  8257856 call_user_func_array ( )    ../bootstrap.php.cache:1419
7   0.3575  8258280 MyApp\FileBundle\Controller\DefaultController->putFileAction( ) ../bootstrap.php.cache:1419
8   0.3685  8656272 Symfony\Bundle\FrameworkBundle\Controller\Controller->createForm( ) ../DefaultController.php:54
9   0.3690  8729192 Symfony\Component\Form\FormFactory->create( )   ../Controller.php:156
10  0.3736  9065128 Symfony\Component\Form\FormBuilder->getForm( )  ../FormFactory.php:36
11  0.3913  9410904 Symfony\Component\Form\FormBuilder->getForm( )  ../FormBuilder.php:201
(...)
282 1.9549  43960328    Symfony\Component\Form\FormBuilder->resolveChildren( )  ../FormBuilder.php:196
283 1.9558  43972408    Symfony\Component\Form\FormBuilder->create( )   ../FormBuilder.php:256
284 1.9558  43972456    Symfony\Component\Form\FormFactory->createNamedBuilder( )   ../FormBuilder.php:118
285 1.9559  43972456    Symfony\Component\Form\ResolvedFormType->createBuilder( )   ../FormFactory.php:87
286 1.9559  43972456    Symfony\Component\OptionsResolver\OptionsResolver->resolve( )   ../ResolvedFormType.php:116
287 1.9561  43981576    Symfony\Component\OptionsResolver\Options->all( )   ../OptionsResolver.php:240
288 1.9561  43984640    Symfony\Component\OptionsResolver\Options->resolve( )   ../Options.php:255
289 1.9561  43984688    Symfony\Component\OptionsResolver\LazyOption->evaluate( )   ../Options.php:399
290 1.9562  43985024    Closure->__invoke( )    ../LazyOption.php:67
291 1.9562  43985056    Symfony\Component\Form\Extension\Core\Type\FormType->Symfony\Component\Form\Extension\Core\Type\{closure}( )    ../LazyOption.php:67
292 1.9562  43985104    Symfony\Component\OptionsResolver\Options->offsetGet( ) ../LazyOption.php:181
293 1.9562  43985104    Symfony\Component\OptionsResolver\Options->get( )   ../Options.php:290
294 1.9562  43985104    Symfony\Component\OptionsResolver\Options->resolve( )   ../Options.php:184
295 1.9562  43985152    Symfony\Component\OptionsResolver\LazyOption->evaluate( )   ../Options.php:399
296 1.9562  43985488    Closure->__invoke( )    ../LazyOption.php:67
297 1.9562  43985520    Symfony\Component\Form\Extension\Core\Type\DateTimeType->Symfony\Component\Form\Extension\Core\Type\{closure}( )    ../LazyOption.php:67
298 1.9562  43985568    Symfony\Component\OptionsResolver\Options->offsetGet( ) ../LazyOption.php:195
299 1.9562  43985568    Symfony\Component\OptionsResolver\Options->get( )   ../Options.php:290
´´´

renderFormView HTTP error code

The documentation mentions that the renderFormView returns a 412 Precondition Failed error if the form validation fails. The only thing that is similar to that method is renderForm and it doesn't seem to set any status code.

  1. Can you tell me where exactly is this being handled?
  2. In your opinion, wouldn't 422 Unprocessable Entity be more appropriate? See Section 11.2 for a more descriptive argument.

Addendum: in terms of Backbone.js, any 4xx error will trigger the error handlers, so it's really more of a conceptual point.

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.