symfony2 Form Collections: adding new item to collection from a pre populated list - forms

I'm building a basic fantasy football game as a base for learning symfony2 and doctrine.
i have an the following entities:
Contest - holds various contests that can be entered
Player - holds details on players that can be selected for the
contest
ContestEntry: holds a contest Entry comprising of contestID, UserID and
selection of players.
I'm currently building the player selection page.
I have a controller that gets a list of available players for selection, which is rendered as a table, each row has a button, i will use javascript so that when the selection button is clicked it will add the player to the form collection and then save the player in my ManyToMany table, when the form is submitted.
New players cannot be added or deleted they can only be selected from the list of available players.
The main page for the contest entry:
{% extends '#Design/Default/layout-main.html.twig' %}
{% block title %}Enter Contest{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link href="{{ asset('bundles/design/css/homebase.css') }}" type="text/css" rel="stylesheet"/>
<link href="{{ asset('bundles/design/css/menu.css') }}" type="text/css" rel="stylesheet"/>
<link href="{{ asset('bundles/design/css/bootstrap.css') }}" rel="stylesheet"/>
{% endblock %}
{% block header %}
{% include 'DesignBundle:Default/Menus:homepage-menu.html.twig' %}
<div class="row">
{% if app.session.flashBag.has('success') %}
<div class="alert alert-success">
×
{% for msg in app.session.flashBag.get('success') %}
{{ msg }}
{% endfor %}
</div>
{% endif %}
</div>
<section>
<div class="container">
<div class="row jumbotron color-bkg">
{% include 'GameBundle:Contest/Partial:contest_details.html.partial.twig' %}
<div class="row">
<div class="col-md-6">
<div class="row centered-form">
{{ contest.contestID }}
{{ render(controller('GameBundle:FantasyPlayer:getPlayers', {'contestID': contest.contestID})) }}
</div>
</div>
<div class="col-md-6">
Select Your Team!!
Need to render the contestEntry Form here with the ability to add a player when a player from the list is clicked
In addition each added player will have a remove button to remove the player from the team.
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
</script>
{% endblock %}
The controller that gets the list of players available for selection:
class FantasyPlayerController extends Controller
{
public function getPlayersAction($contestID)
{
$em = $this->get('doctrine.orm.entity_manager');
//get the repos
$contestRepo = $em->getRepository('GameBundle:Contest');
$scheduleGroupRepo = $em->getRepository('GameBundle:ScheduleGroup');
$playerRepo = $em->getRepository('DataBundle:DailyFantasyPlayer');
$contest = $contestRepo->find($contestID);
//var_dump($contest);die();
//get the ScheduleGroup
$scheduleGroup = $contest->getScheduleGroup();
//var_dump($scheduleGroup);die();
//get schedules
$schedules = $scheduleGroupRepo->getScheduleGroup($scheduleGroup);
//var_dump($schedules);die();
//get teams playing in schedules
$teamsArray = array();
foreach($schedules as $schedule){
$hometeam = $schedule->getHomeTeam();
$awayteam = $schedule->getAwayTeam();
array_push($teamsArray, $hometeam);
array_push($teamsArray, $awayteam);
}
//Get players matching the teams
$dfp = $playerRepo->getAvaliablePlayersByTeam($teamsArray);
return $this->render('GameBundle:Forms:player-list.html.twig', array(
'dfp' => $dfp
));
}
The Twig Tempate for the playerlist:
<table class="table table-condensed">
<thead>
<tr>
<th>Position</th>
<th>Player</th>
<th>Opp</th>
<th>Opp Rank</th>
<th>FPPG</th>
<th>Salary</th>
<th></th>
</tr>
</thead>
<tbody>
{% for p in dfp %}
<tr id="player-{{ p.playerID }}" data-player="{'position':'{{ p.position }}', 'salary': '{{ p.salary }}', 'pid': '{{ p.playerID }}'">
<td>{{ p.position }}</td>
<td>{{ p.name }}</td>
<td>{{ p.team }} # {{ p.opponent }}</td>
<td>{{ p.opponentRank }}</td>
<td>{{ p.opponentPositionRank }}</td>
<td>{{ p.salary }}</td>
<td>
<div class="btn btn-sm btn-default player-select">Select</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
The controller is called in my twig template for entering the contest.
My ContestEntry entity has the ManyToMany Defined (its unidirectional as i only need to get players that have been assigned to a contest entry)
/**
* ContestEntry
*
* #ORM\Table(name="fp_contestEntry")
* #ORM\Entity(repositoryClass="FantasyPro\GameBundle\Entity\ContestEntryRepository")
*/
class ContestEntry
{
public function __construct()
{
$this->user = new ArrayCollection();
$this->contest = new ArrayCollection();
$this->players = new ArrayCollection();
}
/**
* #var integer
*
* #ORM\Column(type="integer", name="id")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(type="integer", nullable=true, name="user")
*
*/
private $user;
/**
* #var integer
*
*
* #ORM\ManyToOne(targetEntity="FantasyPro\GameBundle\Entity\Contest", inversedBy="contestEntries")
* #ORM\Column(type="integer", nullable=false, name="contest")
*
*/
private $contest;
/**
*
*
* #ORM\ManyToMany(targetEntity="FantasyPro\DataBundle\Entity\Player", inversedBy="contestEntries")
* #ORM\JoinTable(
* name="Player_ContestEntry",
* joinColumns={#ORM\JoinColumn(name="contestEntryID", referencedColumnName="id", nullable=false)},
* inverseJoinColumns={#ORM\JoinColumn(name="playerID", referencedColumnName="playerID", nullable=false)}
* )
*/
private $players;
/**
* #var boolean
*
* #ORM\Column(type="boolean", nullable=true, name="locked")
*/
private $locked;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #return int
*/
public function getUser()
{
return $this->user;
}
/**
* #param int $user
*/
public function setUser( $user = null )
{
$this->user = $user;
}
/**
* #return int
*/
public function getContest()
{
return $this->contest;
}
/**
* #param int $contest
*/
public function setContest( $contest = null )
{
$this->contest = $contest;
}
/**
* #return int
*/
public function getPlayers()
{
return $this->players;
}
/**
* #param int $players
*/
public function setPlayers( $players = null )
{
$this->players = $players;
}
/**
* #return boolean
*/
public function isLocked()
{
return $this->locked;
}
/**
* #param boolean $locked
*/
public function setLocked( $locked )
{
$this->locked = $locked;
}
}
From other questions i've read here, i need to be using a form collection.
class ContestEntryType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', 'hidden')
->add('contest', 'hidden')
->add('locked', 'hidden')
->add('players', 'collection', array(
'type' => 'hidden',
'allow_add' => true)
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FantasyPro\GameBundle\Entity\ContestEntry'
));
}
/**
* #return string
*/
public function getName()
{
return 'fantasypro_gamebundle_contestentry';
}
}
Ive read the docs on symfony form collections but am having trouble understanding it, as it does not seem to give any examples of what i'm trying to do, adding a <TR> with the players information, along with a hidden field containing the playerID (to be stored in my ManyToMany Table).
Can anybody give me some pointers in the right direction?

Related

Still unable with Symfony to validate collection form

I'm using SYMFONY 5 and have setup a collection form where the user is enabled to create a service and add numerous sub-services to the service. This is working fine and the user can add/edit/show/delete services and also sub-services.
Now I want to validate if a new sub-service is added and the form is submitted, the language of the sub-service item must be the one of the service item. If not, the database will not be updatet and the user will get an error message (see screen-shot). This is also working fine with one exception:
I cannot achieve to stick the error-message to the failed sub-service! It appears on every sub-service.
Here the definition of my entity service:
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ServicesRepository")
*/
class Services
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=3)
*/
private $sprache;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $transid;
/**
* #ORM\Column(type="string", length=200)
*/
private $header;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $body;
/**
* #ORM\OneToMany(targetEntity="App\Entity\SubServices", mappedBy="services",cascade={"persist"})
* #Assert\Valid()
*/
private $subServices;
public function __construct()
{
$this->subServices = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public function getTransId(): ?int
{
return $this->transid;
}
public function setTransId(int $transid): self
{
$this->transid = $transid;
return $this;
}
public function getSprache(): ?string
{
return $this->sprache;
}
public function setSprache(string $sprache): self
{
$this->sprache = $sprache;
return $this;
}
public function getHeader(): ?string
{
return $this->header;
}
public function setHeader(string $header): self
{
$this->header = $header;
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
public function setBody(?string $body): self
{
$this->body = $body;
return $this;
}
/**
* #return Collection|SubServices[]
*/
public function getSubServices(): Collection
{
return $this->subServices;
}
public function addSubService(SubServices $subService): self
{
if (!$this->subServices->contains($subService)) {
$this->subServices[] = $subService;
$subService->setServices($this);
}
return $this;
}
public function removeSubService(SubServices $subService): self
{
if ($this->subServices->contains($subService)) {
$this->subServices->removeElement($subService);
// set the owning side to null (unless already changed)
if ($subService->getServices() === $this) {
$subService->setServices(null);
}
}
return $this;
}
}
As you can see, in the service entity I have put #Assert\Valid() for the subservices.
Here the definition of the sub-service entity:
<?php
namespace App\Entity;
use App\Validator\SubServiceSprache;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\SubServicesRepository")
* #SubServiceSprache()
*/
class SubServices
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $transsubid;
/**
* #ORM\Column(type="string", length=3)
*/
private $sprache;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Services", inversedBy="subServices")
*/
private $services;
/**
* #ORM\Column(type="string", length=200)
*/
private $header;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $body;
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id)
{
$this->id = $id;
}
public function getTransSubId(): ?int
{
return $this->transsubid;
}
public function setTransSubId(int $transsubid): self
{
$this->transsubid = $transsubid;
return $this;
}
public function getsprache(): ?string
{
return $this->sprache;
}
public function setsprache(string $sprache): self
{
$this->sprache = $sprache;
return $this;
}
public function getServices(): ?Services
{
return $this->services;
}
public function setServices(?Services $services): self
{
$this->services = $services;
return $this;
}
public function getHeader(): ?string
{
return $this->header;
}
public function setHeader(string $header): self
{
$this->header = $header;
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
public function setBody(?string $body): self
{
$this->body = $body;
return $this;
}
}
As you can see, for the whole class subservices I have put the validation #SubServiceSprache().
Here is the definition of the validator SubServiceSprache:
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class SubServiceSprache extends Constraint
{
public function validatedBy()
{
return \get_class($this).'Validator';
}
public function getTargets()
{
//PROPERTY_CONSTRAINT wenn zB. EMAIL geprüft werden soll
//CLASS_CONSTRAINT wenn ganze Entity geprüft werden soll
// jeweils das Objekt (EMAIL od. ganzes Klassenobjekt wird übergeben
return self::CLASS_CONSTRAINT;
}
}
And here the validation logic in SubServiceSpracheValidator:
<?php
namespace App\Validator;
use App\Entity\Services;
use App\Entity\SubServices;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Contracts\Translation\TranslatorInterface;
class SubServiceSpracheValidator extends ConstraintValidator
{
private $em;
private $subservice;
private $translator;
public function __construct(EntityManagerInterface $em, TranslatorInterface $translator)
{
$this->em = $em;
$this->translator = $translator;
$this->subservice = new SubServices();
}
public function validate($object, Constraint $constraint)
{
// Ist die Sprache des SubService die des Service?
if ($object instanceof SubServices) {
if($object->getServices()->getSprache() != $object->getsprache()){
// Message Translation
$message = $this->translator->trans('subservice_sprachcheck',
['subsprache' => object->getsprache(),'servsprache' => $object->getServices()->getsprache()]
);
// Assign message
$this->context->buildViolation($message)
->atPath('sprache')
->addViolation();
}
}
}
}
Here is a snippet of the form class for service:
->add('subservices', CollectionType::class,
array('entry_type' => SubservicesFormType::class,
'label' => false,
'entry_options' => array('label' => false),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'error_bubbling' => false,
))
->add('save', SubmitType::class,
array('label' => 'Sichern',
'attr' => array('class' => 'buttonsave')
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Services::class,
'error_bubbling' => false,
//'newid' => false,
]);
}
and here the one for the subservices:
class SubservicesFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('sprache', LanguageType::class,
array('label' => 'Sprache',
'disabled' => false,
'attr' => array('class' => 'form-control'),
'choice_loader' => NULL,
'choices' => ['DEUTSCH' => 'de', 'ENGLISCH' => 'en'],
'choice_translation_domain' => true,
))
->add('header', TextType::class,
array('label' => 'Überschrift',
'attr' => array('class' => 'form-control')))
->add('body', TextareaType::class,
array('label' => 'Beschreibung',
'attr' => array('class' => 'form-control')))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => SubServices::class,
'validation_groups' => ['Default'],
]);
}
}
and at last my twig-file:
{% extends 'base.html.twig' %}
{% import _self as formMacros %}
{% block title %}UB Mollekopf{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ absolute_url('/css/ub_styles.css') }}" type="text/css" media="all">
<link rel="stylesheet" href="{{ absolute_url('css/font-awesome.css') }}">
{% endblock %}
{% macro printSubserviceRow(SubservicesFormType) %}
<td class="subserviceformsprache">{{ form_widget(SubservicesFormType.sprache) }}</td>
<td class="subserviceformheader">{{ form_widget(SubservicesFormType.header) }}</td>
<td class="subserviceformbody">{{ form_widget(SubservicesFormType.body) }}</td>
<td class="subserviceformaction"></td>
{% endmacro %}
{% block body %}
<div class="tableserviceedit">
{{ form_start(form) }}
<div class="tableheadereditservice">
<table id="editserviceheader">
<tr style="white-space: nowrap">
<th style="width: 100%; padding-left: 0.5em">{% trans %}Ändern Service{% endtrans %} {{ form_widget(form.id) }}</th>
</tr>
</table>
</div>
<div class="tablebodyeditservice">
<table id="editservicesingleheader">
<tr>
<th style="width: 3.5em;">{% trans %}Sprache{% endtrans %}</th>
<th style="width: 12em">{% trans %}Überschrift{% endtrans %}</th>
<th style="width: 15em">{% trans %}Beschreibung{% endtrans %}</th>
</tr>
<tr class="editserviceheader">
<td class="serviceformsprache">
{{ form_errors(form.sprache) }}
{{ form_widget(form.sprache) }}
</td>
<td class="serviceformheader">
{{ form_errors(form.header) }}
{{ form_widget(form.header) }}
</td>
<td class="serviceformbody">
{{ form_errors(form.body) }}
{{ form_widget(form.body) }}
</td>
</tr>
</table>
<div class="tablebodysubservices">
<table id="subservices">
<thead>
<tr>
<th style="width: 6em;">{% trans %}Sprache{% endtrans %}</th>
<th style="width: 22.2em">{% trans %}Überschrift{% endtrans %}</th>
<th style="width: 15em">{% trans %}Beschreibung{% endtrans %}</th>
</tr>
</thead>
<tbody id="collector" data-prototype="{{ formMacros.printSubserviceRow(form.subservices.vars.prototype)|e('html_attr') }}">
{% for subservice in form.subservices %}
<tr>
<td colspan="4">
<span style="color:red" > {{ form_errors(form) }}</span>
</td>
</tr>
<tr>
<td class="subserviceformsprache">
{{ form_widget(subservice.sprache) }}
</td>
<td class="subserviceformheader">
{{ form_widget(subservice.header) }}
</td>
<td class="subserviceformbody">
{{ form_widget(subservice.body) }}
</td>
<td class="subserviceformaction"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="tablefooter" id="fussbereichnewservice">
<div class="btnfooter">{{ form_widget(form.save) }} <button type="" class="buttonabort">{% trans %}Abbruch{% endtrans %}</button></div>
</div>
{{ form_end(form) }}
{#</div>#}
{{ include('inc/navbar_bottom.html.twig') }}
{% endblock %}
{% block javascripts %}
<script src="{{ absolute_url('/js/main.js') }}"></script>
{% if locale == 'en' %}
<script src="{{ absolute_url('/js/subservicesen.js') }}"></script>
{% else %}
<script src="{{ absolute_url('/js/subservices.js') }}"></script>
{% endif %}
{% endblock %}
In the twig-template file I have tried serveral posibilities:
if I code {{ form_errors(form) }} the error-message will appear on every sub-service, if I code {{ form_errors(form.sprache) }} no error message will appear at all.
Does anybody have an idea to solve this?
and what happens if you try
{% for subservice in form.subservices %}
{{ form_errors(subservice ) }}
...
%}

laravel 5.4 Form doesn't show anything on localhost. and it also shows no any error message. It's blank

Description:
I downloaded and installed Form from https://laravelcollective.com/docs/5.4/html and configure all files as per instructions.
Collective\Html\HtmlServiceProvider::class,
'Form' => Collective\Html\FormFacade::class,
'Html' => Collective\Html\HtmlFacade::class,
Problem:
My Route http://127.0.0.1:8000/admin/product/create is showing a blank page. In actual it should show me a Form.
There is no any error message in all code or localhost.
HTML CODE:
#extends('admin.layout.admin')
#section('content')
<h3>Add Product</h3>
<div class="row">
<div class="col-md-8 col-md-offset-2">
{!! Form::open(['route' => 'product.store', 'method' => 'POST', 'files' => true, 'data-parsley-validate'=>'']) !!}
<div class="form-group">
{{ Form::label('name', 'Name') }}
{{ Form::text('name', null, array('class' => 'form-control','required'=>'','minlength'=>'5')) }}
</div>
<div class="form-group">
{{ Form::label('description', 'Description') }}
{{ Form::text('description', null, array('class' => 'form-control')) }}
</div>
<div class="form-group">
{{ Form::label('price', 'Price') }}
{{ Form::text('price', null, array('class' => 'form-control')) }}
</div>
<div class="form-group">
{{ Form::label('size', 'Size') }}
{{ Form::select('size', [ 'small' => 'Small', 'medium' => 'Medium','large'=>'Large'], null, ['class' => 'form-control']) }}
</div>
<div class="form-group">
{{ Form::label('category_id', 'Categories') }}
{{ Form::select('category_id', $categories, null, ['class' => 'form-control','placeholder'=>'Select Category']) }}
</div>
<div class="form-group">
{{ Form::label('image', 'Image') }}
{{ Form::file('image',array('class' => 'form-control')) }}
</div>
{{ Form::submit('Create', array('class' => 'btn btn-default')) }}
{!! Form::close() !!}
</div>
</div>
#endsection
product.php code
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable=['name','description','size','category_id','image','price'];
public function category()
{
return $this->belongsTo(Category::class);
}
public function images()
{
return $this->hasMany(ProductImage::class);
}
public function reviews()
{
return $this->hasMany(ProductReview::class);
}
public function getStarRating()
{
$count = $this->reviews()->count();
if(empty($count)){
return 0;
}
$starCountSum=$this->reviews()->sum('rating');
$average=$starCountSum/ $count;
return $average;
}
}
ProductsController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProductsController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param int $id
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
Your create() is empty,so it will not show anything. It should be like this
public function create()
{
return view('form');
}
form is name on your view.

Symfony : How can i display and handle many edit form on sigle page

I'm stuck with the following problem.
I'm using Symfony 4.2.3 to build a forum ( a piece of a bigger project )
I've made a ForumController who handle :
Forum Home page ( show forumCategories with their forumSubCategories )
Forum Category page ( show selected forumCategory with her forumSubCategories )
Forum Sub Category page ( show selected forumSubCategory with her forumTopics )
Forum Topic page ( show selected forumTopic with her forumMessage( i.e. reaction ) )
What i want is to implement an edit button who not redirect to an edit page but $(this).slidedown an edit form in a div. This button is display only if the ForumMessage author is the current login user.
So if this user have reply many time to the topic, I need just as many buttons ( and form ). After click on edit, the page can be reload and entity update.
For create / edit with redirection i've understood how to.
The problem was, how to handle an unknown number of edit MessageType form ( and so unknown form id ).
I've try to create an array of ForumMessage link to an array of MessageType Form.
But when i have to give the .createView() of each form to twig, my brain glitch.
So please, how can i have many edit form on sigle page( each link to the refered entity ) and handle them in my controller to .flush modification ?
I already implement JS function for the diplaying of the button and linked div.
The showTopic method of my ForumController.php :
/**
* #Route("/forum/category={idCategory}/subCategory={idSubCategory}/topic={idTopic}", name="topic")
* #ParamConverter("topic", options={"id" = "idTopic"})
* #param $idCategory
* #param $idSubCategory
* #param $idTopic
* #param Request $request
* #param ObjectManager $manager
* #param UserInterface $user
* #return \Symfony\Component\HttpFoundation\Response
* #throws \Exception
*/
public function showTopic($idCategory, $idSubCategory, $idTopic, Request $request, ObjectManager $manager, UserInterface $user = null) {
$topic = $this->getDoctrine()->getRepository(ForumTopic::class)->find($idTopic);
$userMessages = $this->getDoctrine()->getRepository(ForumMessage::class)->findBy([
'author' => "Kaarie",
'forumTopic' => $topic
]);
// Nouveau message sur un topic
$message = new ForumMessage();
$form = $this->createForm(ForumMessageType::class, $message);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$message->setAuthor($user->getUsername())
->setCreatedAt(new \DateTime())
->setForumTopic($topic);
$manager->persist($message);
$manager->flush();
return $this->redirectToRoute('topic', [
'idCategory' => $idCategory,
'idSubCategory' => $idSubCategory,
'idTopic' => $topic->getId(),
]);
}
// Editer un message
$editMessage = new ForumMessage();
$editForm = $this->createForm(ForumMessageType::class, $editMessage);
$editForm->handleRequest($request);
if($editForm->isSubmitted() && $editForm->isValid()) {
$manager->persist($editMessage);
$manager->flush();
return $this->redirectToRoute('topic', [
'idCategory' => $idCategory,
'idSubCategory' => $idSubCategory,
'idTopic' => $topic->getId(),
]);
}
return $this->render('forum/showTopic.html.twig',[
'idCategory' => $idCategory,
'idSubCategory' => $idSubCategory,
'topic' => $topic,
'messageForm' => $form->createView(),
'editForm' => $editForm->createView(),
'userMessage' => $userMessages,
]);
}
The class MessageType in MessageType.php
class ForumMessageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content')
->add('submit', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ForumMessage::class,
]);
}
}
The twig part who display Message from showTopic.html.twig
<ul>
{% for message in topic.ForumMessages %}
<li>
{{ message.author }},</br>
{{ message.content }}
{% if app.user %}
{% if is_granted("ROLE_MODERATOR") %}
<button>Moderate</button> {# TODO: moderation d'un message #}
{% endif %}
{% if app.user.username == message.author %}
<div class="alert alert-danger" style="margin: 1em; display: none">
<h3>Etidé votre réponse :</h3>
{{ form_start(editForm) }}
{{ form_row(editForm.content) }}
{{ form_row(editForm.submit, {'label': 'Editer'}) }}
{#<button type="submit" class="btn btn-primary">Editer</button>#}
{{ form_end(editForm) }}
</div>
<button id="buton_EditTopic">Modifier</button>
{% endif %}
{% endif %}
</li>
{% endfor %}
</ul>
For any other ressources please ask me !
My approach would be (maybe some tweaking is necessary, didnt test it)
Short Hand explanation:
The list will only contain containers. When you edit one message, you load only the form and push that to this specific container. If you edit this and press save, it would send the form per ajax request to the controller. If the form is valid, it will return then a json repsonse instead of html ...
Controller:
/**
* #Route("/forum/category={idCategory}/subCategory={idSubCategory}/topic={idTopic}", name="topic")
* #ParamConverter("topic", options={"id" = "idTopic"})
* #param $idCategory
* #param $idSubCategory
* #param $idTopic
* #param Request $request
* #return \Symfony\Component\HttpFoundation\Response
* #throws \Exception
*/
public function showTopic(
$idCategory,
$idSubCategory,
$idTopic,
Request $request,
ObjectManager $manager,
UserInterface $user = null
)
{
$topic = $this->getDoctrine()->getRepository(ForumTopic::class)->find($idTopic);
$userMessages = $this->getDoctrine()->getRepository(ForumMessage::class)->findBy([
'author' => "Kaarie",
'forumTopic' => $topic
]);
return $this->render('forum/showTopic.html.twig',[
'idCategory' => $idCategory,
'idSubCategory' => $idSubCategory,
'topic' => $topic,
'userMessage' => $userMessages,
]);
}
/**
* With this, you can create and mod Topics
* #Route("/forum/messages/{forumMessage}/mod-message", name="message.mod", defaults={"forumMessage":0})
* #IsGranted("ROLE_USER")
* #param Request $request
* #param ForumMessage $forumMessage
* #return mixed
*/
public function modTopic(
Request $request,
Objectmanager $manager,
ForumMessage $forumMessage=null
)
{
if($formMessage == null) {
$forumMessage = new ForumMessage();
/* set Additional Info here, maybe User, IP Adress or whatever */
}
$editForm = $this->createForm(ForumMessageType::class, $forumMessage);
$editForm->handleRequest($request);
if($editForm->isSubmitted() && $editForm->isValid()) {
$manager->persist($forumMessage);
$manager->flush();
return new JsonRepsonse(['status'=>true, 'message' => "ForumMessage save successfull"]);
}
return $this->render('mod.message.html.twig',[
'messageForm' => $editForm->createView(),
]);
}
FormType:
class ForumMessageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ForumMessage::class,
]);
}
}
list.html.twig
<ul>
{% for message in topic.ForumMessages %}
<li>
{{ message.author }},</br>
{{ message.content }}
{% if app.user %}
{% if is_granted("ROLE_MODERATOR") %}
<button>Moderate</button> {# TODO: moderation d'un message #}
{% endif %}
{% if app.user.username == message.author %}
<div id="modMessageContainer{{ message.id }}" class="alert alert-danger" style="margin: 1em; display: none">
</div>
<button onclick="modMessage(this);"
data-attr-url="{{ path('message.mod'.{'forumMessage':message.id}) }}"
data-attr-container="#modMessageContainer{{ message.id }}"
>Modifier</button>
{% endif %}
{% endif %}
</li>
{% endfor %}
</ul>
<script>
function modMessage(element)
{
$.ajax({
url: $(element).attr('data-attr-url'),
success: function(data) {
$($(element).attr('data-attr-container')).html(data).show();
}
});
}
function saveMessage(element)
{
var container = $(element).attr('data-attr-container');
$.ajax({
url: $(element).attr('data-attr-url'),
type:'POST',
data: $(container +' form').serialize(),
success: function(data) {
if(typeof data == 'object' && data instanceof Object && !(data instanceof Array)) {
if(data.status) {
location.reload()
} else {
alert(data.message);
}
} else {
$(container).show();
$('#modMessage').replaceWith($(data).find('#modMessage'));
}
}
});
}
</script>
mod.html.twig
<div>
<div id="modMessage">
<h3>Etidé votre réponse :</h3>
{{ form_start(editForm) }}
{{ form_row(editForm.content) }}
{{ form_row(editForm.submit, {'label': 'Editer'}) }}
{#<button type="submit" class="btn btn-primary">Editer</button>#}
{{ form_end(editForm) }}
<div style="text-align:right">
<button onclick="saveMessage(this);"
type="button"
class="btn btn-success"
data-attr-container="modMessageContainer{{ message.id }}"
data-attr-url="{{ path('message.mod', {'forumMessage':message.id}) }}"
>Save</button>
</div>
</div>
</div>

Symfony2 Create a Post calls ShowAction

I am "transplanting" my old website code into the new Symfony2.6. I have a Controller to render a form and create posts as in a blog. Then it is submited (POST method) in a second controller once it is valid. When I call the route to initialize the controller, for some reason I ignore, it also calls the ShowAction($slug), and it fails since the post it is not yet created and hence it does not have any $slug parameter. Why is it calling this showAction? Despite I type the url to just display the form.
Here is the error log at the line that starts the failure:
1. in src/Blog/BlogBundle/Services/PostManager.php at line 80
2. at PostManager ->findBySlug ('create_post') in src/Blog/BlogBundle/Controller PostController.php at line 55
3. at PostController ->ShowAction ('create_post')
4. at call_user_func_array (array(object(PostController), 'ShowAction'), array('create_post')) in app/bootstrap.php.cache at line 3020
I do not want to call the ShowAction.
Here is the controller code:
/**
* Show a Post
*
* #param string $slug
*
* #throws NotFoundHttpException
* #return array
*
* #Route("/{slug}")
* #Template()
*/
public function ShowAction($slug)
{
$post = $this->getPostManager()->findBySlug($slug);
$form_comment = $this->createForm(new CommentType());
return array(
'post' => $post,
'form_comment' => $form_comment->createView()
);
}
/**
* Displays a form to create a new Post entity.
*
* #Route("/new_post", name="_blog_backend_post_new")
* #Template()
*/
public function newAction()
{
$form = $this->createForm(new PostType(), new PostModel(new Post()));
return array(
'form' => $form->createView(),
);
}
/**
* Creates a new Post entity.
*
* #Route("/create_post", name="_blog_backend_post_create")
* #Method("POST")
* #Template("BlogBundle:Backend/Post:new.html.twig")
*/
public function createAction()
{
$request = $this->getRequest();
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new PostType(), new PostModel(new Post()));
$formHandler = new PostHandler($form, $request, new Post(), $em);
if ($formHandler->process()) {
return $this->redirect($this->generateUrl('_blog_backend_post'));
}
return array(
'form' => $form->createView(),
);
}
I do not know if its necessary but here are the involved templates:
* #Template("BlogBundle:Backend/Post:new.html.twig"):
{% extends "::base.html.twig" %}
{% block content %}
<form class="well" action="{{ url('_blog_backend_post_create') }}" method="post" {{ form_enctype(form) }}>
{% include 'BlogBundle:Backend/Post:edit.form.html.twig' with {'form': form } %}
</form>
{% endblock %}
BlogBundle:Backend/Post:edit.form.html.twig:
{% if form_errors(form) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form) }}
</div>
{% endif %}
<p>
<label for="">Title</label>
{% if form_errors(form.title) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form.title) }}
</div>
{% endif %}
{{ form_widget(form.title, { 'attr': {'class': 'w100'} }) }}
</p>
<p>
<label for="">Body</label>
{% if form_errors(form.body) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form.body) }}
</div>
{% endif %}
{{ form_widget(form.body, { 'attr': {'class': 'w100'} }) }}
</p>
<p>
<button class="btn" type="submit">Save</button>
<a class="btn" href="{{ url('_blog_backend_post') }}">Cancel</a>
</p>
{{ form_rest(form) }}
What I am missing and how can I correct it? Thank you in advanced.
The router matches the first route possible, in this case "/{slug}" where slug="create_post".
There are at least two solutions, where the first is the easiest, the second is the one I recommend:
Cut/Paste your showAction to the bottom of your file, in that case router will match /create_post to the createAction first
You can exclude this like: #Route("/{slug}", requirements={"slug" = "^(?<!create_post).+"})
The problem is that you are posting to /create_post which in turn matches the route /{slug}. slug is being set to 'create_post'. With the Symfony 2 router the first match wins and thus your showAction method is called.
Consider setting your ShowAction path to: '/show/{slug}'.
Or you could move your ShowAction down to the bottom of your controller file. That is a bit dangerous because you might forget and add a different action later. But either way will work.

Error on form submission: The CSRF token is invalid. Please try to resubmit the form [duplicate]

This question already has answers here:
The CSRF token is invalid. Please try to resubmit the form
(15 answers)
Closed 7 years ago.
I've been trying to submit a form which adds a Question object into the db.
But everytime I do, the error "The CSRF token is invalid. Please try to resubmit the form" shows up.
On my form's content field, I've attached this plugin which is an editor same as Stack Overflow's.
In my form's tag field, I've attached this one for tag autocompletion.
Here's my controller code:
/**
* Creates a new Question entity.
*
* #Route("/ask", name="question_create")
* #Method("POST")
* #Template("VerySoftAskMeBundle:Question:ask.html.twig")
*/
public function createAction(Request $request) {
$entity = new Question();
$form = $this->createCreateForm($entity);
$tags = $this->getDoctrine()->getRepository('VerySoftAskMeBundle:Tag')->findAll();
date_default_timezone_set('Asia/Manila');
$entity->setDateOfPost(new \DateTime());
$entity->setOwner($this->getUser());
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('question_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
'tags' => $tags
);
}
/**
* Creates a form to create a Question entity.
*
* #param Question $entity The entity
*
* #return Form The form
*/
private function createCreateForm(Question $entity) {
$form = $this->createForm(new QuestionType(), $entity, array(
'action' => $this->generateUrl('question_create'),
'method' => 'POST',
'em' => $this->getDoctrine()->getEntityManager()
));
$form->add('submit', 'submit', array('label' => 'Ask'));
return $form;
}
/**
*
* #Route("/ask", name="ask")
* #Security( "has_role( 'ROLE_USER' )" )
* #Method("GET")
* #Template
*/
public function askAction() {
$tags = $this->getDoctrine()->getRepository('VerySoftAskMeBundle:Tag')->findAll();
$entity = new Question();
$form = $this->createCreateForm($entity);
return array(
'entity' => $entity,
'form' => $form->createView(),
'tags' => $tags
);
}
I've made a Data Transformer for my tag field which turns the input tags into tag objects.
class TagTransFormer implements DataTransformerInterface {
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om) {
$this->om = $om;
}
/**
* Transforms an object (issue) to a string (number).
*
* #return ArrayCollection
*/
public function transform($tags) {
return $tags;
}
/**
* Transforms a string (number) to an object (issue).
*
* #param string $number
*
* #return ArrayCollection
*
* #throws TransformationFailedException if object (issue) is not found.
*/
public function reverseTransform($ids) {
$tags = array();
if (!$ids) {
return null;
}
$repo = $this->om
->getRepository('VerySoftAskMeBundle:Tag');
$idsArray = explode(",", $ids);
foreach ($idsArray as $id) {
$tags[] = $repo->findOneByName($id);
}
return $tags;
}
}
Here's my form class:
class QuestionType extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$entityManager = $options['em'];
$transformer = new TagTransFormer($entityManager);
$builder
->add('title', 'text')
->add('content', 'textarea')
->add($builder->create('tags', 'text')
->addModelTransformer($transformer)
);
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'VerySoft\AskMeBundle\Entity\Question'
))
->setRequired(array(
'em',
))
->setAllowedTypes(array(
'em' => 'Doctrine\Common\Persistence\ObjectManager',
));
}
/**
* #return string
*/
public function getName() {
return 'verysoft_askmebundle_question';
}
}
My Twig Template:
<div id="askDiv" style="padding-bottom: 90px;">
{{ form_start(form, { 'attr' : { 'novalidate' : 'novalidate', 'class' : 'col-md-offset-3 form-control-static col-md-7' } }) }}
<div class="col-lg-12" style="padding: 0px; margin-bottom: 30px;">
<span class="askLabels col-lg-1 text-left">{{ form_label(form.title) }}</span>
{{form_widget(form.title, { 'attr' : { 'class' : 'form-control col-lg-11' } })}}
</div>
{{ form_widget(form.content, { 'attr' : { 'class' : 'col-lg-12' } }) }}
<div class="col-lg-12" style="padding: 0px; margin-top: 20px;">
<label class="col-lg-1 text-left askLabels" for="tagField">Tags</label>
<div class="col-lg-8">
{{ form_widget(form.tags) }}
</div>
{% if app.user.reputation >= 100 %}
<a id="addTag" title="Add New Tag" data-toggle="tooltip modal" data-placement="left" class="col-lg-3" href="#"><i class="fa fa-plus-circle"></i></a>
<div id="mymodal" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">Add New Tag</h4>
</div>
<div class="modal-body">
<label for="tagName">Tag Name: </label>
<input id="tagName" class="form-control" type="text"/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Add Tag</button>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<div style="margin-top: 20px; ">
{{ form_widget(form.submit, { 'attr' : { 'class' : 'col-md-offset-4 col-md-4 btn btn-primary' } }) }}
</div>
<p>
title error{{ form_errors(form.title) }}
</p>
<p>
content error{{ form_errors(form.content) }}
</p>
<p>
tag error{{ form_errors(form.tags) }}
</p>
<p>
form error{{ form_errors(form) }}
</p>
Scripts:
$(document).ready(function(){
$("textarea").pagedownBootstrap();
var zeTags = ["{{ tags|join('", "')|raw }}"];
$('#verysoft_askmebundle_question_tags').tagit({
availableTags: zeTags,
tagLimit: 5,
beforeTagAdded: function(event, ui) {
if ($.inArray(ui.tagLabel, zeTags) == -1)
return false;
}
});
});
You missed
{{ form_rest(form) }}
Symfony2 has a mechanism that helps to prevent cross-site scripting: they generate a CSRF token that have to be used for form validation. Here, in your example, you're not displaying (so not submitting) it with form_rest(form). Basically form_rest(form) will "render" every field that you didn't render before but that is contained into the form object that you've passed to your view. CSRF token is one of those values.
For newer versions of Symonfy, e.g. 2.4+ you would use the newer form_end(form), which automatically renders all fields not rendered as well as the CSRF token.
Per the documentation:
form_end() - Renders the end tag of the form and any fields that have not yet been rendered. This is useful for rendering hidden fields and taking advantage of the automatic CSRF Protection.