I have a wired situation here. On persisting symfony inserts multiple records instead of one. I can't find any problem with my code and I am not sure how to debug this error because it seems all fine.
Logic behind application is that user can select two bus station and create a route. Also he needs to define price and group (minimum and maximum people). Additionally, user needs to select bus vehicles that will drive this particular route
Does someone knows where is the problem?
Here is the output of entity when form is submitted
StationStandardPrice {#553 ▼
-id: null
-company: Company {#549 ▶}
-busVehicleGroupSize: BusVehicleGroupSize {#1233 ▶}
-departureStation: Stations {#1247 ▶}
-destinationStation: Stations {#1236 ▶}
-currency: Currencies {#1015 ▶ …2}
-price: 99.0
-busVehicles: ArrayCollection {#554 ▼
-elements: array:2 [▼
0 => BusVehicle {#1023 ▶}
1 => BusVehicle {#1208 ▶}
]
}
}
This is entity of route (shorted version)
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \AppBundle\Entity\Company
*
* #ORM\ManyToOne(targetEntity="Company")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
* })
*/
private $company;
/**
* #var \AppBundle\Entity\BusVehicleGroupSize
*
* #ORM\ManyToOne(targetEntity="BusVehicleGroupSize")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="bus_vehicle_group_size_id", referencedColumnName="id")
* })
*/
private $busVehicleGroupSize;
/**
* #var \AppBundle\Entity\Stations
*
* #ORM\ManyToOne(targetEntity="Stations")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="departure_station_id", referencedColumnName="id")
* })
*/
private $departureStation;
/**
* #var \AppBundle\Entity\Stations
*
* #ORM\ManyToOne(targetEntity="Stations")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="destination_station_id", referencedColumnName="id")
* })
*/
private $destinationStation;
/**
* #var \AppBundle\Entity\Currencies
*
* #ORM\ManyToOne(targetEntity="Currencies")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="currency_id", referencedColumnName="id")
* })
*/
private $currency;
/**
* #var float
*
* #ORM\Column(name="price", type="decimal", precision=10, scale=2, nullable=false)
*/
private $price;
/**
* Many groups can have many bus vehicles
*
* #ORM\ManyToMany(targetEntity="BusVehicle", inversedBy="busVehicleGroup")
* #ORM\JoinTable(name="standard_station_price_bus_groups",
* joinColumns={#ORM\JoinColumn(name="station_standard_price_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="bus_vehicle_id", referencedColumnName="id")}
* )
**/
private $busVehicles;
/**
* Add bus vehicle
* #param BusVehicle $busVehicles
*/
public function addBusVehicles(BusVehicle $busVehicles)
{
if ($this->busVehicles->contains($busVehicles)) {
return;
}
$this->busVehicles->add($busVehicles);
$busVehicles->addBusVehicleGroup($this);
}
/**
* Remove bus vehicle
* #param BusVehicle $busVehicles
*/
public function removeBusVehicles(BusVehicle $busVehicles)
{
if (!$this->busVehicles->contains($busVehicles)) {
return;
}
$this->busVehicles->remove($busVehicles);
$busVehicles->removeBusVehicleGroup($this);
}
Controller:
/**
* Creates a new stationStandardPrice entity.
* #Template
*/
public function newAction(Request $request)
{
$stationStandardPrice = new Stationstandardprice();
$form = $this->createForm(StationStandardPriceType::class, $stationStandardPrice, array(
'user' => $this->getUser()
)
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($stationStandardPrice);
$em = $this->getDoctrine()->getManager();
$em->persist($stationStandardPrice);
$em->flush();
$this->addFlash('success', 'admin.stationstandardprice.created');
//return $this->redirectToRoute('stationstandardprice_show', array('id' => $stationStandardPrice->getId()));
}
return [
'stationStandardPrice' => $stationStandardPrice,
'form' => $form->createView(),
];
}
Database output:
Many to Many table status:
EDIT 1: Added twig
{% extends 'AdminBundle::layout.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link href="{{ asset('resources/public/css/datatables.min.css', 'busrent_admin') }}" rel="stylesheet"/>
<link href="{{ asset('resources/public/css/smart_wizard.min.css', 'busrent_admin') }}" rel="stylesheet"/>
<link href="{{ asset('resources/public/css/smart_wizard_theme_dots.min.css', 'busrent_admin') }}" rel="stylesheet"/>
{% endblock %}
{% block title %} {{ 'admin.stationstandardprice.new.title'|trans }} {% endblock %}
{% block breadcrumb %}
<div class="col-lg-10">
<h2>{{ 'admin.stationstandardprice.new.title'|trans }}</h2>
<ol class="breadcrumb">
<li>
{{ 'admin.dashboard.index.title'|trans }}
</li>
<li>
{{ 'admin.stationstandardprice.index.title'|trans }}
</li>
<li class="active">
<strong>{{ 'admin.stationstandardprice.new.title'|trans }}</strong>
</li>
</ol>
</div>
<div class="col-lg-2">
</div>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>{{ 'admin.stationstandardprice.new.title'|trans }}</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
</div>
</div>
<div class="ibox-content">
<div class="row">
<div class="col-lg-12">
{{ form_start(form) }}
<div id="smartwizard">
<ul>
<li><a href="#step-1">Step Title<br/>
<small>Step description</small>
</a>
</li>
<li><a href="#step-2">Step Title<br/>
<small>Step description</small>
</a>
</li>
<li><a href="#step-3">Step Title<br/>
<small>Step description</small>
</a>
</li>
</ul>
<div>
<div id="step-1" class="">
{{ form_row(form.departureStation) }}
{{ form_row(form.destinationStation) }}
</div>
<div id="step-2" class="">
{{ form_row(form.busVehicleGroupSize) }}
{{ form_row(form.price) }}
{{ form_row(form.currency) }}
</div>
<div id="step-3" class="">
{% if app.user.company is not null %}
<div class="ibox-content">
<table id="busVehicleTable" class="table table-striped">
<thead>
<tr>
<th>{{ 'admin.busvehicle.form.licencePlate'|trans }}</th>
<th>{{ 'admin.busvehicle.form.chassisNumber'|trans }}</th>
<th>{{ 'admin.busvehicle.form.passengerSeatsNumber'|trans }}</th>
<th>{{ 'admin.busvehicle.form.busType'|trans }}</th>
<th>{{ 'admin.busvehicle.form.emissionClass'|trans }}</th>
<th>{{ 'admin.busvehicle.form.fullDayPrice'|trans }}</th>
<th>{{ 'admin.busvehicle.form.halfDayPrice'|trans }}</th>
<th>{{ 'admin.busvehicle.form.pricePerKm'|trans }}</th>
<th>{{ 'admin.busvehicle.form.amenities'|trans }}</th>
<th>{{ 'actions'|trans }}</th>
</tr>
</thead>
<tbody>
{% set inc = 0 %}
{% for busVehicle in app.user.company.busVehicle %}
<tr>
<td>{{ busVehicle.licencePlate }}</td>
<td>{{ busVehicle.chassisNumber }}</td>
<td>{{ busVehicle.passengerSeatsNumber }}</td>
<td>{{ busVehicle.busType.type }}</td>
<td>{{ busVehicle.emissionClass.name }}</td>
<td>{{ busVehicle.fullDayPrice }}</td>
<td>{{ busVehicle.halfDayPrice }}</td>
<td>{{ busVehicle.pricePerKm }}</td>
<td>
<i style="cursor: pointer;" class="fa fa-search"
aria-hidden="true"
data-toggle="collapse"
data-target="#amenities{{ inc }}"></i>
</td>
<td>
<div id="addBusVehicleDiv{{ busVehicle.id }}">
<button data-id="{{ busVehicle.id }}" href="#"
class="addBusVehicle btn btn-primary btn-sm">
<i class="fa fa-plus"></i>
<span class="bold"> Add bus vehicle</span>
</button>
</div>
<div id="removeBusVehicleDiv{{ busVehicle.id }}"
style="display: none;">
<div id="removeBusVehicleDiv{{ busVehicle.id }}">
<button data-id="{{ busVehicle.id }}"
href="#"
class="removeBusVehicle btn btn-danger btn-sm">
<i class="fa fa-times"></i>
<span class="bold"> Remove bus vehicle</span>
</button>
</div>
</div>
</td>
</tr>
<tr id="amenities{{ inc }}" class="collapse">
<td>
>{{ 'admin.busvehicle.form.amenities'|trans }}:
</td>
<td>
<div>
<p>
{% for busAmenity in busVehicle.busVehiclesAmenities %}
{% if loop.last %}
{{ busAmenity.name }}
{% else %}
{{ busAmenity.name }},
{% endif %}
{% endfor %}
</p>
</div>
</td>
</tr>
{% set inc = inc + 1 %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script src="{{ asset('resources/public/js/jquery.smartWizard.min.js', 'busrent_admin') }}"></script>
<script src="{{ asset('resources/public/js/busVehicleGroup.js', 'busrent_admin') }}"></script>
{% endblock %}
JS:
// Smart Wizard
$('#smartwizard').smartWizard({
selected: 0, // Initial selected step, 0 = first step
keyNavigation:true, // Enable/Disable keyboard navigation(left and right keys are used if enabled)
autoAdjustHeight:true, // Automatically adjust content height
cycleSteps: false, // Allows to cycle the navigation of steps
backButtonSupport: true, // Enable the back button support
showStepURLhash: true,
useURLhash: true, // Enable selection of the step based on url hash
lang: { // Language variables
next: 'Next',
previous: 'Previous'
},
toolbarSettings: {
toolbarPosition: 'bottom', // none, top, bottom, both
toolbarButtonPosition: 'right', // left, right
showNextButton: true, // show/hide a Next button
showPreviousButton: true, // show/hide a Previous button
toolbarExtraButtons: [
$('<button disabled id="finishForm" type="submit"></button>').text('Finish')
.addClass('btn btn-info')
]
},
anchorSettings: {
anchorClickable: false, // Enable/Disable anchor navigation
enableAllAnchors: false, // Activates all anchors clickable all times
markDoneStep: true, // add done css
enableAnchorOnDoneStep: true // Enable/Disable the done steps navigation
},
contentURL: null, // content url, Enables Ajax content loading. can set as data data-content-url on anchor
disabledSteps: [], // Array Steps disabled
errorSteps: [], // Highlight step with errors
theme: 'dots',
transitionEffect: 'fade', // Effect on navigation, none/slide/fade
transitionSpeed: '400'
});
$("#smartwizard").on("showStep", function(e, anchorObject, stepNumber, stepDirection) {
if (stepNumber == 2){
$('#finishForm').attr("disabled", false);
}
else{
$('#finishForm').attr("disabled", true);
}
});
the problem seems to be in:
public function addBusVehicles(BusVehicle $busVehicles)
{
if ($this->busVehicles->contains($busVehicles)) {
return;
}
$this->busVehicles->add($busVehicles);
$busVehicles->addBusVehicleGroup($this);
}
when call
$busVehicles->addBusVehicleGroup($this);
your are adding a new route to the relation, try without calling this method.
Related
I'm using KnpPaginatorBundle for a page.
In this page, I use a hand-created filter, which gives this
So if I enter something in the form, it will process the request and show as agreed.
The problem is when I ask to sort by 'ID'.
This problem occurs when I make a filter on the name or service, and I ask to order by ID
I have the following error message:
This form should not contain extra fields
What should I do to stop this error?
My controller is :
/**
* Undocumented function
*
* #Route("/rh3", name="rh_index3")
*
* #param PaginatorInterface $paginator
* #param Request $request
* #param ObjectManager $manager
* #param UserRepository $repo
* #return void
*/
public function index3(PaginatorInterface $paginator, Request $request, UserRepository $repo)
{
$search = new UserSearch();
$form = $this->createForm(UserSearchType::class, $search);
$form->handleRequest($request);
$users = $repo->getUsersSearch($search);
$pagination = $paginator->paginate(
$users,
$request->query->getInt('page', 1),
7
);
return $this->render('rh/index3.html.twig', [
'pagination' => $pagination,
'form' => $form->createView(),
]);
}
And my Twig:
{% extends "base.html.twig" %}
{% block body %}
<div class="jumbotron">
<div class="container">
{{form_start(form)}}
<div class="form-row align-items-end">
<div class="col">
{{form_row(form.name)}}
</div>
<div class="col">
{{form_row(form.service)}}
</div>
<div class="col">
<div class="form-group">
<button type="submit" class="btn btn-primary">Rechercher</button>
</div>
</div>
</div>
{{form_end(form)}}
</div>
</div>
<table class="table table-hover align-items-center">
<thead>
<tr>
<th>{{ knp_pagination_sortable(pagination, 'Id', 'u.id') }}</th>
<th>Nom</th>
<th>Service</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for user in pagination %}
<tr {% if loop.index is odd %} class="color" {% endif %}>
<td>{{user.id}}</td>
<td>{{user.fullName}}</td>
<td>{{user.groupe.nom}}</td>
<td>
Voir
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="navigation">
{{knp_pagination_render(pagination)}}
</div>
{% endblock %}
Thanks for your help !
in your UserSearchType add this option:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'allow_extra_fields' => true,
));
}
I was tasked of making a shift managing web app.
I have used OpenSkedge (an open-source web app).
One of my tasks is that each user should have a Skill Set.
I have added a functionality where you add these Skill Sets, and I have another view which displays information for the user.
User Controller:
/**
* Finds and displays a User entity.
*
* #param integer $id ID of user
*
* #return \Symfony\Component\HttpFoundation\Response
*/
public function viewAction($id)
{
$em = $this->getDoctrine()->getManager();
if (is_null($id)) {
$id = $this->getUser()->getId();
}
$entity = $em->getRepository('OpenSkedgeBundle:User')->find($id);
VarDumper::dump($entity);
if (!$entity instanceof User) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$deleteForm = $this->createDeleteForm($id);
return $this->render('OpenSkedgeBundle:User:view.html.twig', array(
'entity' => $entity,
'skillset' => $entity->getSkillset(),
'delete_form' => $deleteForm->createView(),
));
}
SkillSet Entity:
<?php
// src/OpenSkedge/AppBundle/Entity/SkillSet.php
namespace OpenSkedge\AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\KeyValueStore\Mapping\Annotations as KeyValue;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* OpenSkedge\AppBundle\Entity\SkillSet
*
* #ORM\Table(name="os_skillset")
* #ORM\Entity
*/
class SkillSet
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $skillSet;
/**
* #ORM\Column(type="string", length=100)
*/
private $skillNumber;
/**
* #ORM\ManyToOne(targetEntity="user", inversedBy="sset")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
*/
private $user;
public function getSkillSet()
{
return $this->skillSet;
}
public function setSkillSet($s){
$this->skillSet = $s;
return $this;
}
public function getSkillnumber()
{
return $this->skillNumber;
}
public function setSkillnumber($nm){
$this->skillNumber = $nm;
return $this;
}
public function getUser(){
return $this->user;
}
public function setUser(\OpenSkedge\AppBundle\Entity\User $u = null){
$this->user = $u;
return $this;
}
}
View Form:
{% extends 'OpenSkedgeBundle:Dashboard:index.html.twig' %}
{% block title %}{{ app_brand_name() }} - {{ entity.name }}{% endblock %}
{% block modulecontent %}
<div class="span12">
<h3>{{ entity.name }}</h3>
{% if app.user.id == entity.id %}
<div class="btn-group header-control">
<a class="btn" href="{{ path('user_edit', { 'id': entity.id }) }}"><i class="icon-pencil"></i> Edit</a>
</div>
{% elseif is_granted('ROLE_ADMIN') %}
<form action="{{ path('user_delete', { 'id': entity.id }) }}" method="post">
<div class="btn-group header-control">
<a class="btn" href="{{ path('user_edit', { 'id': entity.id }) }}"><i class="icon-pencil"></i> Edit</a>
{% if app.user.id != entity.id and delete_form is defined %}
{{ form_widget(delete_form) }}
<button class="btn btn-danger" type="submit"><i class="icon-trash icon-white"></i> Delete</button>
{% endif %}
</div>
</form>
{% endif %}
<div class="btn-group header-control">
<a class="btn btn-info" href="{{ path('user_schedules', { 'id': entity.id }) }}">Schedules</a>
<a class="btn btn-info" href="{{ path('user_positions', { 'id': entity.id }) }}">Positions</a>
<a class="btn btn-info" href="{{ path('user_supervisors', { 'id': entity.id }) }}">Supervisors</a>
<a class="btn btn-info" href="{{ path('user_employees', { 'id': entity.id }) }}">Employees</a>
</div>
<table class="table table-condensed">
<tbody>
<tr>
<th>Username</th>
<td>{{ entity.username }}</td>
</tr>
<tr>
<th>Name</th>
<td>{{ entity.name }}</td>
</tr>
<tr>
<th>Role</th>
<td>{{ entity.group.name }}</td>
</tr>
{% if entity.workphone %}
<tr>
<th>Work Phone</th>
<td>{{ entity.workphone }}</td>
</tr>
{% endif %}
{% if entity.homephone %}
<tr>
<th>Home Phone</th>
<td>{{ entity.homephone }}</td>
</tr>
{% endif %}
{% if entity.location %}
<tr>
<th>Location</th>
<td>{{ entity.location }}</td>
</tr>
{% endif %}
<tr>
<th>Email</th>
<td>{{ entity.email }}</td>
</tr>
{% if entity.min %}
<tr>
<th>Minimum Hours</th>
<td>{{ entity.min }}</td>
</tr>
{% endif %}
{% if entity.max %}
<tr>
<th>Maximum Hours</th>
<td>{{ entity.max }}</td>
</tr>
{% endif %}
{% if entity.hours %}
<tr>
<th>Requested Hours</th>
<td>{{ entity.hours }}</td>
</tr>
{% endif %}
{% if entity.notes %}
<tr>
<th>Notes</th>
<td>{{ entity.notes }}</td>
</tr>
{% endif %}
{% if is_granted('ROLE_ADMIN') %}
{% if entity.supnotes %}
<tr>
<th>Supervisor Notes</th>
<td>{{ entity.supnotes }}</td>
</tr>
{% endif %}
{% endif %}
<tr>
<th>Color</th>
<td><span class="label" style="background-color: {{ entity.color }};">{{ entity.color }}</span></td>
</tr>
<tr>
<th>Active</th>
<td>{% if entity.isactive == 1 %}yes{% else %}no{% endif %}</td>
</tr>
</tbody>
</table>
<h3>Skillsets</h3>
<ul class="skillset">
{% for set in skillset %}
{% endfor %}
</ul>
</div>
{% endblock %}
The following shows a dump of a User Entity:
As you can see, the user has a set property which includes the Skill Sets which have been added in the new form.
However, the following doesn't display such skillsets in the page:
<h3>Skillsets</h3>
<ul class="skillset">
{% for set in skillset %}
{% endfor %}
</ul>
How can I solve this problem?
I am using Symfony 3.4.7 . I work with 3 linked entites, Articles, Categories, an ArticlesCategories. ArticlesCategories is the relation table.
To manage an article and their relation articleCategories, I use the class CollectionType that I add to the FormType ArticlesType.
In my display I add and I remove a relation articlesCatégroies to an Article.
This is the code of an entity Articles :
/**
* Articles
*
* #ORM\Table(name="articles")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ArticlesRepository")
* #UniqueEntity("codeArticle", message="Déjà utilisé")
*/
class Articles
{
public function __construct()
{
$this->articlesCategories = new ArrayCollection();
}
/**
* #var string
*
* #ORM\Column(name="code_article", type="string", length=10)
* #ORM\Id
*/
private $codeArticle;
//... other variables
/**
* #var ArticlesCategories
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\ArticlesCategories", mappedBy="codeArticle", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $articlesCategories;
// other getters and setters
/**
* Add articlesCategorie
*
* #param ArticlesCategories $articleCategorie
*
* #return Articles
*/
public function addArticlesCategorie(ArticlesCategories $articleCategorie){
$this->articlesCategories[] = $articleCategorie;
$articleCategorie->setCodeArticle($this);
return $this;
}
/**
* remove articlesCategorie
*
* #param ArticlesCategories $articlesCategorie
*/
public function removeArticlesCategorie(ArticlesCategories $articlesCategorie){
$this->articlesCategories->removeElement($articlesCategorie);
}
public function setArticlesCategories($articlesCategories){
$this->articlesCategories = $articlesCategories;
return $this;
}
/**
* Get articlesCategories
*
* #return Collection
*/
public function getArticlesCategories(){
return $this->articlesCategories;
}
}
This the code of an entity ArticlesCategories :
/**
* ArticlesCategories
*
* #ORM\Table(name="articles_categories", uniqueConstraints={#ORM\UniqueConstraint(name="unique_codeArticle_codeCategorie", columns={"code_article_id", "code_categorie_id"})})
* #ORM\Entity(repositoryClass="AppBundle\Repository\ArticlesCategoriesRepository")
* #UniqueEntity(fields={"codeArticle", "codeCategorie"}, message="Cette relation est déjà enregistré")
*/
class ArticlesCategories
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Articles", inversedBy="articlesCategories", cascade={"remove"})
* #ORM\JoinColumn(referencedColumnName="code_article", nullable=false)
*/
private $codeArticle;
/**
* #var string
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Categories", cascade={"remove"})
* #ORM\JoinColumn(referencedColumnName="reference", nullable=false)
*/
private $codeCategorie;
/**
* #var string
*
* #ORM\Column(name="critere_rech_1", type="string", length=45, nullable=true)
*/
private $critereRech1;
//... getters and setters
}
This is the code of my form ArticlesType :
class ArticlesType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('codeArticle')
//...autres attributs
->add('articlesCategories', CollectionType::class, array(
'entry_type' => ArticlesCategoriesType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'label' => 'categories',
'attr' => array('class' => 'collection-articlesCategories'),
'auto_initialize' => true,
'by_reference' => false
));
}
}
This the action who I called when I want to edit an article :
class ArticlesController extends Controller
{
/**
* Displays a form to edit an existing article entity.
*
* #Route("/edit/{codeArticle}", name="articles_edit", defaults={"codeArticle" = null} )
* #Method({"GET", "POST"})
*/
public function editAction(Request $request, Articles $article = null)
{
if($article != null){
/*$em = $this->getDoctrine()->getManager();
$article = $em->getRepository('AppBundle:Articles')->find($id_article);*/
$deleteForm = $this->createDeleteForm($article);
$editForm = $this->createForm('AppBundle\Form\ArticlesType', $article);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
// récupère tous les articles même ceux qui viennent d'être ajouter par la fonction JS add
$articlesCategories = $article->getArticlesCategories();
dump($articlesCategories);
foreach($articlesCategories as $ac){
// si l'article est null
if($ac->getCodeArticle() == null){
// on attribue à la relation articleCategorie l'article que l'on modifi
$ac->setCodeArticle($article);
}
}
$this->getDoctrine()->getManager()->flush();
}
return $this->render('articles/edit.html.twig', array(
'article' => $article,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
else{
return $this->forward('AppBundle:articles:new', [
'message' => 'Pour éditer un article il faut d\'abord l\'afficher.'
]
);
}
}
}
I display the form on the Twig view.
And I use Jquery to add and remove of the form a relation ArticlesCategories.
This is what the form of my article looks like with its collection:
My display
The probleme that I meet is, when i add a relation and I click on "Editer", I can see that the relation is create in my database. But when I remove a relation and I click on "Editer", I can see that my relation is not remove on the database.
I don't see where is my mistake.
I hope I am clear.
Thank you for help.
[EDIT]
This is my view twig :
{% extends 'articles/gerer.html.twig' %}
{% import 'articles/fields.html.twig' as formMacros %}
{% block contenu %}
{{ form_start(delete_form) }}
<input type="submit" value="Supprimer">
{{ form_end(delete_form) }}
{{ form_start(edit_form) }}
{{ form_label(edit_form.codeArticle, "Référence article") }}
{{ form_errors(edit_form.codeArticle) }}
{{ form_widget(edit_form.codeArticle, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.description, "Description") }}
{{ form_errors(edit_form.description) }}
{{ form_widget(edit_form.description, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.ecotaxe) }}
{{ form_errors(edit_form.ecotaxe) }}
{{ form_widget(edit_form.ecotaxe, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.qteMaxCde) }}
{{ form_errors(edit_form.qteMaxCde) }}
{{ form_widget(edit_form.qteMaxCde, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.publication) }}
{{ form_errors(edit_form.publication) }}
{{ form_widget(edit_form.publication, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.designation) }}
{{ form_errors(edit_form.designation) }}
{{ form_widget(edit_form.designation, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.taxonomie) }}
{{ form_errors(edit_form.taxonomie) }}
{{ form_widget(edit_form.taxonomie, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.referenceStock) }}
{{ form_errors(edit_form.referenceStock) }}
{{ form_widget(edit_form.referenceStock, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.articleRegroupement) }}
{{ form_errors(edit_form.articleRegroupement) }}
{{ form_widget(edit_form.articleRegroupement, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.articleAssocie1) }}
{{ form_errors(edit_form.articleAssocie1) }}
{{ form_widget(edit_form.articleAssocie1, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.articleAssocie2) }}
{{ form_errors(edit_form.articleAssocie2) }}
{{ form_widget(edit_form.articleAssocie2, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.articleAssocie3) }}
{{ form_errors(edit_form.articleAssocie3) }}
{{ form_widget(edit_form.articleAssocie3, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.seuilDegressif) }}
{{ form_errors(edit_form.seuilDegressif) }}
{{ form_widget(edit_form.seuilDegressif, {'attr': {'class': 'form-control'}}) }}
{{ form_label(edit_form.tauxDegressif) }}
{{ form_errors(edit_form.tauxDegressif) }}
{{ form_widget(edit_form.tauxDegressif, {'attr': {'class': 'form-control'}}) }}
<div class="row well well-sm js-collection-articles-categories-wrapper" data-prototype="{{ formMacros.printCategorieRow(edit_form.articlesCategories.vars.prototype)|e('html_attr') }}"
data-index="{{ edit_form.articlesCategories|length }}">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
Catégories
</div>
{% for ac in edit_form.articlesCategories %}
{{ formMacros.printCategorieRow(ac) }}
{% endfor %}
<a href="#" class="js-collection-articles-categories-add">
<span class="fa fa-plus-circle"> Ajouter une catégorie</span>
</a>
</div>
<input type="submit" value="Modifier" />
{{ form_end(edit_form) }}
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
jQuery(document).ready(function () {
var $wrapper = $('.js-collection-articles-categories-wrapper');
$wrapper.on('click', '.js-remove-articles-categories', function (e) {
e.preventDefault();
$(this).closest('.js-collection-articles-categories-item')
.fadeOut()
.remove();
})
$wrapper.on('click', '.js-collection-articles-categories-add', function (e) {
e.preventDefault();
var prototype = $wrapper.data('prototype');
var index = $wrapper.data('index');
var newForm = prototype.replace(/__name__/g, index);
$wrapper.data('index', index + 1);
$(this).before(newForm);
})
});
</script>
{% endblock %}
I use a form_theme to display an element of my collection, this is the code :
{% import _self as formMacros %}
{% macro printCategorieRow(ac) %}
<div class="js-collection-articles-categories-item">
{{ form_errors(ac) }}
<div class="col-lg-2 col-md-2 col-sm-2 col-xs-6">
{{ form_label(ac.codeCategorie) }}
{{ form_widget(ac.codeCategorie, {'attr': {'class': 'select_articles_categories'}}) }}
</div>
<div class="col-lg-10 col-md-10 col-sm-10 col-xs-12">
<a href="#" class="js-remove-articles-categories pull-right">
<span class="fa fa-close">Supprimer une catégorie</span>
</a>
<table class="table">
<tr>
<th>Critères</th>
<th>Valeurs</th>
</tr>
<tr id="new_article_table_critere1">
<td><div id="critere1">{{ form_label(ac.critereRech1) }}</div></td>
<td>{{ form_widget(ac.critereRech1, {'attr': {'class': 'form-control'}}) }}</td>
</tr>
<tr id="new_article_table_critere2">
<td><div id="critere2">{{ form_label(ac.critereRech2) }}</div></td>
<td>{{ form_widget(ac.critereRech2, {'attr': {'class': 'form-control'}}) }}</td>
</tr>
<tr id="new_article_table_critere3">
<td><div id="critere3">{{ form_label(ac.critereRech3) }}</div></td>
<td>{{ form_widget(ac.critereRech3, {'attr': {'class': 'form-control'}}) }}</td>
</tr>
<tr id="new_article_table_critere4">
<td><div id="critere4">{{ form_label(ac.critereRech4) }}</div></td>
<td>{{ form_widget(ac.critereRech4, {'attr': {'class': 'form-control'}}) }}</td>
</tr>
</table>
</div>
</div>
{% endmacro %}
Make the following change in your entity, 'Articles'.
Note: Don't forget to add a setter function (ex: setcodeArticle()) for 'codeArticle' in your entity, 'ArticlesCategories'
public function removeArticlesCategorie(ArticlesCategories $articlesCategorie){
$this->articlesCategories->removeElement($articlesCategorie);
$articlesCategorie->setcodeArticle(null);
}
I am trying to create a view for display the total sales for all the suppliers and customers of a shop.
The total sales for a customer are easy to obtain, the problem is when I have to create the columns with the brands, I have been looking through this site, doctrine, etc, But I cannot find any solution.
SaleController.php
/**
* #Route("/widget/summarize")
*
* #return Render
*/
public function widgetSummarizeAction()
{
$suppliers = $this->getDoctrine()->getEntityManager()->createQueryBuilder()
->select(['su.name AS name'])
->from('AppBundle:Supplier', 'su')
->getQuery()
->getArrayResult()
;
$data = $this->getDoctrine()->getManager()->createQueryBuilder()->groupBy('c.name')
->select([
's.id AS id',
's.invoiceDate AS invoiceDate',
'c.name AS customer',
'SUM(ROUND(s.amount * s.price, 2)) AS totalSales',
'su.name AS supplier',
])
->from('AppBundle:Sale', 's')
->leftJoin('s.supplierCustomer', 'sc')
->leftJoin('sc.customer', 'c')
->leftJoin('sc.supplier', 'su')
->getQuery()
->getArrayResult()
;
return $this->render('AppBundle::home_widget.html.twig', [
'title' => 'Total Sales',
'icon' => 'money',
'urlList' => $this->generateUrl($this->getUrlList()),
'rowRoute' => $this->getUrlEdit(),
'data' => $data,
'columns' => [
'customer' => 'customer',
'totalSales' => 'Total Sales'
]
]);
home_widget.html.twig
<div class="panel panel-default home-widget">
<div class="panel-heading">
<h2 class="panel-title pull-left">
{% if icon is defined and icon is not empty %}<i class="fa fa-{{ icon }}"></i>{% endif %}
{{ title }}
</h2>
{% if urlList %}
<a class="btn btn-primary pull-right" href="{{ urlList }}">See All</a>
{% endif %}
<div class="clearfix"></div>
</div>
<div class="panel-body">
{% if data is not empty and data is iterable %}
<table class="table table-bordered table-striped">
<thead>
<tr>
{% for column in columns %}
<td>{{ column }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in data %}
{% set href = url(rowRoute, {'id': row.id}) %}
<tr>
{% for key,column in columns %}
<td>
<a href="{{ href }}">
{{ row[key] | raw }}
</a>
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
Maybe try get colulmns name by:
$em = $this->getDoctrine()->getManager();
$columns = $em->getClassMetadata(your_entity_name::class)->getFieldNames();
and loop through the columns in twig template.
<thead>
<tr>
{% for column in columns %}
<td>{{ column }}</td>
{% endfor %}
</tr>
</thead>
I have a private message bundle/entity that allows my users to send messages between them.
Its fields are as follows:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #Assert\NotBlank(message="private_message.title.blank")
* #ORM\Column(name="title", type="string", length=50)
*/
protected $title;
/**
* #Assert\NotBlank(message="private_message.receiver.blank")
* #AcmeAssert\IsHimself(message="private_message.receiver.himself", groups={"new"})
* #ORM\ManyToOne(targetEntity="MedAppBundle\Entity\User")
* #ORM\JoinColumn(referencedColumnName="id")
*/
protected $receiver;
/**
* #ORM\ManyToOne(targetEntity="MedAppBundle\Entity\User")
* #ORM\JoinColumn(referencedColumnName="id")
*/
protected $sender;
/**
* #var string
* #Assert\NotBlank(message="private_message.content.blank")
* #ORM\Column(name="content", type="string")
*/
protected $content;
/**
* #var \DateTime
*
* #ORM\Column(name="sentAt", type="datetime")
*/
protected $sentAt;
/**
* #var boolean
*
* #ORM\Column(name="isSpam", type="boolean")
*/
protected $isSpam = false;
/**
* #var \DateTime
*
* #ORM\Column(name="seenAt", type="datetime",nullable=true)
*/
protected $seenAt = null;
/**
* #ORM\ManyToOne(targetEntity="PrivateMessageBundle\Entity\Message",inversedBy="replies")
* #ORM\JoinColumn(referencedColumnName="id",nullable=true)
*/
protected $replyof;
/**
* #ORM\OneToMany(targetEntity="PrivateMessageBundle\Entity\Message", mappedBy="replyof")
**/
private $replies;
public function __construct() {
$this->replies = new ArrayCollection();
}
Notice the replyof field, it references to another message, and the replies one references to an array of messages. If replyof is null, then the message is not a reply of any message.
I have a twig template with a macro that displays a user's message and all the replies of that message. What I'd like to do is have a reply textfield under each of these, exactly like Gmail has, that allows me to add a reply to each message.
But when I add it to the template, only one is rendered because it has one single Id. How can I add a reply form after each reply? What their FormType should look like?
Here is also my twig template:
{% macro displayReply(reply,replyform) %}
{% import _self as macros %}
{# <li> id: {{ reply.id }} </li>
<li> sent by: {{ reply.sender }} </li>
<li> title: {{ reply.title }} </li>
<li> content: {{ reply.content }} </li>
<li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
reply
<hr> #}
<div class="panel panel-default">
<div class="panel-body">
<div class="message-info">
<input type="hidden" name="messageid" id="messageId" value="{{ reply.id }}">
<div class="message-title clearfix">
<h4 class="pull-left">{{ reply.title }}</h4>
</div>
<hr class="lite-line">
<div class="message-sender clearfix">
<div class="pull-left sender">
{{ reply.sender }}
</div>
<div class="pull-right">
to <b>{{ (reply.receiver==app.user)?'me':reply.receiver }}</b> on <span
class="message-timestamp">{{ reply.sentAt|date('F d, Y H:i:s') }}</span>
<a class="btn btn-start-order" role="button"
href="{{ path('private_message_new',{'msg':reply.id}) }}">Reply</a>
</div>
</div>
<hr class="lite-line">
<div class="message-box clearfix">
<span>{{ reply.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
</div>
{{ form_start(replyform) }}
<input type="submit">
{{ form_end(replyform) }}
</div>
</div>
</div>
{% for reply in reply.replies %}
{% if loop.first %}<div>{% endif %}
{{ macros.displayReply(reply) }}
{% if loop.last %}</div>{% endif %}
{% endfor %}
{% endmacro %}
{% import _self as macros %}
{# use the macro #}
<div class="message-back">
<a class="btn btn-start-order-dark btn-block" role="button"
href="{{ path('private_message',{'page':'inbox'}) }}">
<span class="fa fa-undo"></span> Go back
</a>
</div>
<div class="messages">
<div class="panel panel-default">
<div class="panel-body">
<div class="message-info">
<input type="hidden" name="messageid" id="messageId" value="{{ message.id }}">
<div class="message-title clearfix">
<h4 class="pull-left">{{ message.title }}</h4>
</div>
<hr class="lite-line">
<div class="message-sender clearfix">
<div class="pull-left sender">
{{ message.sender }}
</div>
<div class="pull-right">
to <b>{{ (message.receiver==app.user)?'me':message.receiver }}</b> on <span
class="message-timestamp">{{ message.sentAt|date('F d, Y H:i:s') }}</span> <a
class="btn btn-start-order" role="button"
href="{{ path('private_message_new',{'msg':message.id}) }}">Reply</a>
</div>
</div>
<hr class="lite-line">
<div class="message-box clearfix">
<span>{{ message.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
</div>
{{ form_start(replyform) }}
<input type="submit">
{{ form_end(replyform) }}
</div>
</div>
</div>
</div>
{% for reply in message.replies %}
{% if loop.first %}<div class="replies">{% endif %}
{{ macros.displayReply(reply ,replyform) }}
{% if loop.last %}</div>{% endif %}
{% endfor %}
Notice that I first display the message, then apply the macro to it that displays all its replies as a tree. It will display the replies's replies, too, in a recursive manner, all the way until the leaf nodes. I add a 'replyform' after each, but I'm not sure how the FormType should be.
My reply form type is like this, but I'm pretty sure it is wrong.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('content', 'textarea')
;
}
As for the other fields of the reply, I take care of them in the controller. I think I should be doing this after receiving the message from the form though. Something like this, and get the title, content and replyof from the formdata.
$messages = $this->getDoctrine()->getRepository('PrivateMessageBundle:Message');
$isforme = $messages->findOneBy(array('receiver' => $this->getUser(), 'id' => $msg));
$message = new Message();
$message->setSender($this->getUser());
$message->setSentAt(new \Datetime('now'));
$message->setReplyof($isforme);
$message->setReceiver($isforme->getSender());
$form = $this->createForm(new MessageReplyType($em), $message);
EDIT
Ok, so I made something that works, by adding a hidden field and hardcoding multiple forms instead of using FormTypes, but I still think that this can be done in a better, more reusable way.
<form name="privatemessagebundle_message" method="post" action="" id="{{ reply.id }}">
<div><label for="privatemessagebundle_message_title" class="required">Title</label><input
type="text" id="privatemessagebundle_message_title"
name="privatemessagebundle_message[title]" required="required" maxlength="50"></div>
<div><label for="privatemessagebundle_message_content" class="required">Content</label><textarea
id="privatemessagebundle_message_content"
name="privatemessagebundle_message[content]" required="required"></textarea></div>
<input type="hidden" id="privatemessagebundle_message_replyof"
name="privatemessagebundle_message[replyof]" value="{{ reply.id }}">
<input type="submit">
<input type="hidden" id="privatemessagebundle_message__token"
name="privatemessagebundle_message[_token]"
value="{{ csrf_token('privatemessagebundle_message') }}">
</form>
Anyone got any better ideas?
I did it! I used the answer from this question.
Since I'm using foreach loops and they might be a bit low on performance, anyone with a better idea is welcomed. There is still the bounty to receive.
I'm creating a form for each of my forms through createNamedBuilder. They will have different names, thus different id's and Symfony will render them all. Then, I can render them where I want and handle their request just fine through their unique id taken from the database.
$genforms = $this->genReplyForms($isforme); // run the function for my message
$forms_views = $genforms['views']; // pass to the view
$forms= $genforms['forms']; // handle request...
This is the function that generated the form. It recursively generates them for each reply of my message.
public function genReplyForms(Message $message)
{
$id = $message->getId();
$msgreply[$id] = new Message();
$forms[$id] = $this->container
->get('form.factory')
->createNamedBuilder('form_'.$id, new MessageReplyType(), $msgreply[$id])
->getForm();
$forms_views[$id] = $forms[$id]->createView();
$result = array(array(), array());
$result['forms'][$id] = $forms[$id];
$result['views'][$id] = $forms_views[$id];
if (sizeof($message->getReplies())) {
foreach ($message->getReplies() as $reply) {
$child = $this->genReplyForms($reply);
$result['forms'] = $result['forms'] + $child['forms'];
$result['views'] = $result['views'] + $child['views'];
}
}
return $result;
}
MessageReplyType needs just user input. Everything else is handled in the controller
$builder
->add('title')
->add('content', 'textarea')
;
Also, my simplified twig. I've simplified the macro call, too. Was doing an unnecessary foreach loop for the first message instead of simply passing it to the macro.
{% macro displayReply(reply, forms) %}
{% import _self as macros %}
{# <li> id: {{ reply.id }} </li>
<li> sent by: {{ reply.sender }} </li>
<li> title: {{ reply.title }} </li>
<li> content: {{ reply.content }} </li>
<li> date: {{ reply.sentAt|date('d-m-Y H:i:s') }} </li>
reply
<hr> #}
<div class="panel panel-default">
<div class="panel-body">
<div class="message-info">
<input type="hidden" name="messageid" id="messageId" value="{{ reply.id }}">
<div class="message-title clearfix">
<h4 class="pull-left">{{ reply.title }}</h4>
</div>
<hr class="lite-line">
<div class="message-sender clearfix">
<div class="pull-left sender">
{{ reply.sender }}
</div>
<div class="pull-right">
to <b>{{ (reply.receiver==app.user)?'me':reply.receiver }}</b> on <span
class="message-timestamp">{{ reply.sentAt|date('F d, Y H:i:s') }}</span>
{# <a class="btn btn-start-order" role="button"
href="{{ path('private_message_new',{'msg':reply.id}) }}">Reply</a> #}
</div>
</div>
<hr class="lite-line">
<div class="message-box clearfix">
<span>{{ reply.content | replace({"<script>" : "", "</script>" : ""}) | raw }}</span>
</div>
{{ form_start(forms[reply.id]) }}
<input type="submit">
{{ form_end(forms[reply.id]) }}
{# NU STERGE! #}
{#
<form name="privatemessagebundle_message" method="post" action="" id="{{ reply.id }}">
<div><label for="privatemessagebundle_message_title" class="required">Title</label><input
type="text" id="privatemessagebundle_message_title"
name="privatemessagebundle_message[title]" required="required" maxlength="50"></div>
<div><label for="privatemessagebundle_message_content" class="required">Content</label><textarea
id="privatemessagebundle_message_content"
name="privatemessagebundle_message[content]" required="required"></textarea></div>
<input type="hidden" id="privatemessagebundle_message_replyof"
name="privatemessagebundle_message[replyof]" value="{{ reply.id }}">
<input type="submit">
<input type="hidden" id="privatemessagebundle_message__token"
name="privatemessagebundle_message[_token]"
value="{{ csrf_token('privatemessagebundle_message') }}"></form>#}
{# NU STERGE! #}
</div>
</div>
</div>
{% for reply in reply.replies %}
{% if loop.first %}<div>{% endif %}
{{ macros.displayReply(reply,forms) }}
{% if loop.last %}</div>{% endif %}
{% endfor %}
{% endmacro %}
{% import _self as macros %}
{# use the macro #}
<div class="message-back">
<a class="btn btn-start-order-dark btn-block" role="button"
href="{{ path('private_message',{'page':'inbox'}) }}">
<span class="fa fa-undo"></span> Go back
</a>
</div>
<div class="replies">
{{ macros.displayReply(message, forms) }}
</div>
Again, I'm still looking for better or more efficient alternatives, so please do post them.