tutorials.polifonic.io
  • Sign in
  • Sign up
  • Contents
    • Scenario
    • Database schema
    • The controller
    • The form for entering the participants
  • en
    • en - English

Contents

Contents

IT

  • PHP
  • Symfony
  • Vue.js

Devops

  • Ansible tutorials

Symfony — Dynamic forms and data transformers (part 1)

The symfony application framework includes a Form component that is powerful, flexible and consistent. This component includes 2 particularly convenient features:

  • Configure a form dynamically, e.g. based on the form's underlying object
  • Convert the form's submitted scalar data into something else (e.g. objects)

Unfortunately, these 2 aspects are not directly compatible. Nevertheless, a simple solution exists.

Scenario

In this example, I will use the simple, if not simplistic, scenario below:

  • The app models a club or association.
  • The club has members, which join it at a certain date, and may resign from it at another.
  • The club organizes activities at given dates; only members who are active at that date may participate.

The source code for this tutorial is fully available on github.com.

Database schema

The model defines the following entities:

  • Member: a member of the club; includes her name, given names, join date and if applicable, resignation date
  • Activity: an activity organized by the club at a given date.
  • Participant: represents the participation of a given member to an activity

In this example, I will use the Propel ORM.

This is the database schema:

<?xml version="1.0" encoding="utf-8"?>
<database name="default" namespace="CoopersPeele\ClubBundle\Propel" defaultIdMethod="native">
  <table name="member" idMethod="native">
    <column name="id"          type="integer" primaryKey="true" autoIncrement="true" required="true" />
    <column name="firstName"   type="varchar" size="255" required="true" />
    <column name="lastName"    type="varchar" size="255" required="true" />
    <column name="date_joined" type="date" required="true" />
    <column name="date_left"   type="date" defaultValue="null" />
  </table>
  <table name="activity" idMethod="native">
    <column name="id"   type="integer" primaryKey="true" autoIncrement="true" required="true" />
    <column name="name" type="varchar" size="255" required="true" />
    <column name="date" type="date" required="true" />
  </table>
  <table name="participant" idMethod="native" isCrossRef="true">
    <column name="id" type="integer" primaryKey="true" required="true" autoIncrement="true" />
    <column name="member_id"   type="integer" required="true" />
    <column name="activity_id" type="integer" required="true" />
    <foreign-key foreignTable="member" name="FK_participant_member">
      <reference local="member_id" foreign="id" />
    </foreign-key>
    <foreign-key foreignTable="activity" name="FK_participant_activity">
      <reference local="activity_id" foreign="id" />
    </foreign-key>
  </table>
</database>

The controller

To keep things simple, the input of an activity will be done in 2 stages. In a first step, the date and description of the activity are input, and the activity is saved. In a second step, the participants will be input.

Ideally, we should be able to input and edit the activity's date and participants at the same time, but since the list of eligible participants depends on the activity's date, this just creates an extra layer of complexity. For the purpose of this tutorial, it is unnecessary; in real life, our practice (subject to the client's approval) is to deliver simpler features first, which are less sophisticated but can be deployed faster, and improve them over time based on user feedback.

The logic for creating an activity is straightforward. It's a standard symfony form implementation. After the form has been successfully submitted, the activity is saved and the user is redirected to a page where the activity is displayed and the user can input the participants.

<?php

namespace CoopersPeele\ClubBundle\Controller;

use CoopersPeele\Bundle\ClubBundle\Form\Type\ActivityFormType;
use CoopersPeele\Bundle\ClubBundle\Form\Type\ActivityParticipantsFormType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

/**
 * @route("/activity")
 * @Template()
 */

class ActivityController extends Controller
{
  // ...

  /**
   * @Route(
   *        "/create",
   *        name="activity_create"
   * )
   */

  public function createAction(Request $request)
  {
    $activity = new Activity();

    $form = $this->createForm(
      new ActivityFormType(),
      $activity,
      array(
        'action' => $this->generateUrl($request->attributes->get('_route'))
      )
    );

    $form->handleRequest($request);

    if ($form->isValid()) {
      $activity->save();

      return $this->redirect($this->generateUrl(
        'activity_show',
        array(
          'id' => $activity->getId()
        )
      ));
    }

    return array(
      'activity' => $activity,
      'form' => $form
    );
  }

In order to input the participants, we've opted to include a form in the activity/show page directly. Javascript will be used to toggle the display of the appropriate parts of this form. In the controller, the code for this action is also very straightforward.

/**
   * @Route(
   *        "/{id}",
   *        name="activity_show",
   *        requirements={
   *            "id": "\d+"
   *        }
   * )
   */

  public function showAction(Request $request, $id)
  {
    $activity = ActivityQuery::create()
      ->findPk($id);

    if (!$activity) {
      throw $this->createNotFoundException('Activity not found');
    }

    $form = $this->createForm(
      new ActivityParticipantsFormType(),
      $activity,
      array(
        'action' => $this->generateUrl('activity_show', array('id' => $id)),
      )
    )

    $form->handleRequest($request);

    if ($form->isValid()) {
      $activity->save();
    }

    return array(
      'activity' => $activity,
      'form' => $form,
    );
  }
}

The form for entering the participants

The form used to input an activity's participants will include a single widget mapped to the participants property of the Activity class.

The point of using a mapped widget is that the widget is automatically updated when the form is displayed, and the activity object is automatically updated when the form's data is bound to the form's underlying object upon form submission.

In our form, the participants widget will be a choice widget, with the expanded and multiple options set to true, so that each will be represented by a checkbox.

<?php

namespace CoopersPeele\ClubBundle\Form\Type;

use Propel\PropelBundle\Form\BaseAbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class ActivityParticipantsFormType extends BaseAbstractType
{

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

  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder->add(
      'participants',
      'choice',
      array(
        'multiple' => true,
        'expanded' => true,
      )
    );
  }
}
This page was modified on 15 Feb 2017 at 10:33 AM.

Source

====== Symfony — Dynamic forms and data transformers (part 1) ====== The symfony application framework includes a Form component that is powerful, flexible and consistent. This component includes 2 particularly convenient features: * Configure a form dynamically, e.g. based on the form's underlying object * Convert the form's submitted scalar data into something else (e.g. objects) Unfortunately, these 2 aspects are not directly compatible. Nevertheless, a simple solution exists. ===== Scenario ===== In this example, I will use the simple, if not simplistic, scenario below: * The app models a club or association. * The club has members, which join it at a certain date, and may resign from it at another. * The club organizes activities at given dates; only members who are active at that date may participate. The source code for this tutorial is fully available on [[https://github.com/coopers-peele/club-demo|github.com]]. ===== Database schema ===== The model defines the following entities: * ''Member'': a member of the club; includes her name, given names, join date and if applicable, resignation date * ''Activity'': an activity organized by the club at a given date. * ''Participant'': represents the participation of a given member to an activity In this example, I will use the [[http://propelorm.org|Propel ORM]]. This is the database schema: <code xml> <?xml version="1.0" encoding="utf-8"?> <database name="default" namespace="CoopersPeele\ClubBundle\Propel" defaultIdMethod="native"> <table name="member" idMethod="native"> <column name="id" type="integer" primaryKey="true" autoIncrement="true" required="true" /> <column name="firstName" type="varchar" size="255" required="true" /> <column name="lastName" type="varchar" size="255" required="true" /> <column name="date_joined" type="date" required="true" /> <column name="date_left" type="date" defaultValue="null" /> </table> <table name="activity" idMethod="native"> <column name="id" type="integer" primaryKey="true" autoIncrement="true" required="true" /> <column name="name" type="varchar" size="255" required="true" /> <column name="date" type="date" required="true" /> </table> <table name="participant" idMethod="native" isCrossRef="true"> <column name="id" type="integer" primaryKey="true" required="true" autoIncrement="true" /> <column name="member_id" type="integer" required="true" /> <column name="activity_id" type="integer" required="true" /> <foreign-key foreignTable="member" name="FK_participant_member"> <reference local="member_id" foreign="id" /> </foreign-key> <foreign-key foreignTable="activity" name="FK_participant_activity"> <reference local="activity_id" foreign="id" /> </foreign-key> </table> </database> </code> ===== The controller ===== To keep things simple, the input of an activity will be done in 2 stages. In a first step, the date and description of the activity are input, and the activity is saved. In a second step, the participants will be input. Ideally, we should be able to input and edit the activity's date and participants at the same time, but since the list of eligible participants depends on the activity's date, this just creates an extra layer of complexity. For the purpose of this tutorial, it is unnecessary; in real life, our practice (subject to the client's approval) is to deliver simpler features first, which are less sophisticated but can be deployed faster, and improve them over time based on user feedback. The logic for creating an activity is straightforward. It's a standard symfony form implementation. After the form has been successfully submitted, the activity is saved and the user is redirected to a page where the activity is displayed and the user can input the participants. <code php> <?php namespace CoopersPeele\ClubBundle\Controller; use CoopersPeele\Bundle\ClubBundle\Form\Type\ActivityFormType; use CoopersPeele\Bundle\ClubBundle\Form\Type\ActivityParticipantsFormType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; /** * @route("/activity") * @Template() */ class ActivityController extends Controller { // ... /** * @Route( * "/create", * name="activity_create" * ) */ public function createAction(Request $request) { $activity = new Activity(); $form = $this->createForm( new ActivityFormType(), $activity, array( 'action' => $this->generateUrl($request->attributes->get('_route')) ) ); $form->handleRequest($request); if ($form->isValid()) { $activity->save(); return $this->redirect($this->generateUrl( 'activity_show', array( 'id' => $activity->getId() ) )); } return array( 'activity' => $activity, 'form' => $form ); } </code> In order to input the participants, we've opted to include a form in the activity/show page directly. Javascript will be used to toggle the display of the appropriate parts of this form. In the controller, the code for this action is also very straightforward. <code php> /** * @Route( * "/{id}", * name="activity_show", * requirements={ * "id": "\d+" * } * ) */ public function showAction(Request $request, $id) { $activity = ActivityQuery::create() ->findPk($id); if (!$activity) { throw $this->createNotFoundException('Activity not found'); } $form = $this->createForm( new ActivityParticipantsFormType(), $activity, array( 'action' => $this->generateUrl('activity_show', array('id' => $id)), ) ) $form->handleRequest($request); if ($form->isValid()) { $activity->save(); } return array( 'activity' => $activity, 'form' => $form, ); } } </code> ===== The form for entering the participants ===== The form used to input an activity's participants will include a single widget mapped to the ''participants'' property of the ''Activity'' class. The point of using a mapped widget is that the widget is automatically updated when the form is displayed, and the activity object is automatically updated when the form's data is bound to the form's underlying object upon form submission. In our form, the ''participants'' widget will be a ''choice'' widget, with the ''expanded'' and ''multiple'' options set to ''true'', so that each will be represented by a checkbox. <code php> <?php namespace CoopersPeele\ClubBundle\Form\Type; use Propel\PropelBundle\Form\BaseAbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class ActivityParticipantsFormType extends BaseAbstractType { public function getName() { return 'group_activity_participants'; } public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add( 'participants', 'choice', array( 'multiple' => true, 'expanded' => true, ) ); } } </code>
This website uses cookies to ensure you get the best browsing experience.