I'm learning how yo use symfony, and my problem is, when I want to use the handleRequest function, it did not validate my email en message field, but it's good for name field.
Look the code:
Contact.php entity
<?php
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Contact
{
/**
* #Assert\NotBlank
*/
private $name;
/**
* #Assert\NotBlank
*/
private $email;
/**
* #Assert\NotBlank
*/
private $message;
public function getName()
{
return $this->name;
}
public function getEmail()
{
return $this->email;
}
public function getMessage()
{
return $this->message;
}
public function setName($name)
{
$this->name = $name;
}
public function setEmail($email)
{
$this->name = $email;
}
public function setMessage($message)
{
$this->name = $message;
}
}
?>
BlogController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use App\Entity\Article;
use App\Entity\Contact;
use App\Repository\ArticleRepository;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\HttpFoundation\Request;
class BlogController extends Controller
{
/**
* #Route("/blog", name="blog")
*/
public function blog(ArticleRepository $repo)
{
$articles = $repo->findAll();
return $this->render('blog/index.html.twig', [
'controller_name' => 'BlogController',
'articles' => $articles
]);
}
/**
* #Route("/", name="blog_home")
*/
public function home()
{
return $this->render('blog/home.html.twig');
}
/**
* #Route("/blog/articles/{id}", name="blog_show")
*/
public function show(Article $article)
{
return $this->render('blog/show.html.twig',[
'article' => $article
]);
}
/**
* #Route("/contact", name="blog_contact")
*/
public function contact(Request $request)
{
$contact = new Contact; /* Create the new contact object */
$form = $this->createFormBuilder($contact) /* Creating the form */
->add('name', TextType::class)
->add('email', TextType::class)
->add('message', TextareaType::class)
->getForm();
dump($contact);
$form->handleRequest($request);
dump($contact);
if($form->isSubmitted() && $form->isValid())
{
return $this->redirectToRoute('blog_home');
}
dump($request);
return $this->render('blog/contact.html.twig',[
'form' => $form->createView()
]);
}
}
contact.html.twig
{% extends 'base.html.twig' %}
{% block title %}BLOG - Contact{% endblock %}
{% block body %}
<h2>Me contacter !</h1>
<div class="row">
<div class="col-md-5">
{{ form_start(form) }}
<label>Nom:</label>
{{ form_widget(form.name) }}
<label>Email:</label>
{{ form_widget(form.email) }}
<label>Message:</label>
{{ form_widget(form.message) }}
<br>
<button type="submit" class="btn btn-info">Envoie</button>
{{ form_end(form) }}
</div>
<div class="col-md-1">
</div>
<div class="col-md-6">
<div class="card border-info mb-1" style="max-width: 20rem;">
<div class="card-header">Twitter: #Fergvae</div>
</div>
<div class="card border-dark mb-1" style="max-width: 20rem;">
<div class="card-header">Discord: Fergvae#0730</div>
</div>
<div class="card border-danger mb-1" style="max-width: 20rem;">
<div class="card-header">Youtube: Fergvae</div>
</div>
</div>
</div>
{% endblock %}
The only things that didn't work is the handleRequest it's why I made multiple dump.
you also can look the dumped content, the first is before handle and second after.
Dumped symfony content
Thanks to all people who answer this question !
Ok my bad, I just forget when I copy past to change the ^this->name in de setters ! Excuse me for the disturb
It's great that you have found your solution, but just in case:
If usage for classes, a better practice is to use a FormType, in there you can declare your class on which fields the form should be automatically created. Which Symfony will automatically catch all of the needed fields and will create the form for you, less code written and etc. Or you can create a custom form, and define all of your needed fields, including restrictions as not empty and etc..
To sum it up, if possible keep as less logic in the controllers as possible and move them deeper.
Related
I need to persist my form to the database. I am using Symfony to do this and I have built a response entity:
<?php
namespace App\Entity;
use App\Repository\ResponseRepository;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Validator\Constraints as Assert;
* )
* #ORM\Entity(repositoryClass=ResponseRepository::class)
*/
class Response
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(name="id", type="integer")
* #Groups({"response:read"})
*
*/
private $id;
/**
* #ORM\Column(name="answer", type="string", length=255)
* #Groups({"response:write", "response:read", "session:read"})
* #Assert\NotBlank()
*/
private $answer;
/**
* #ORM\ManyToOne(targetEntity=Session::class, inversedBy="responses")
* #ORM\JoinColumn(nullable=false)
* #Assert\Valid
*/
private $session;
/**
* #ORM\ManyToOne(targetEntity=Question::class, inversedBy="responses")
* #ORM\JoinColumn(nullable=false)
* #Groups({"response:read", "response:write"})
*/
private $question;
// /**
// * #ORM\OneToMany(targetEntity=QuestionsQuestionType::class, mappedBy="response")
// */
// private $questionsQuestionType;
public function __construct()
{
$this->questionsQuestionType = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getAnswer(): ?string
{
return $this->answer;
}
public function setAnswer(string $answer): self
{
$this->answer = $answer;
return $this;
}
public function getSession(): ?Session
{
return $this->session;
}
public function setSession(?Session $session): self
{
$this->session = $session;
return $this;
}
public function getQuestion(): ?Question
{
return $this->question;
}
public function setQuestion(?Question $question): self
{
$this->question = $question;
return $this;
}
A new response function in the ResponseController (please note QuizResponse is an alias of Response entity. This is to not confuse my response entity with Symfony Response) :
/**
* #Route("/new", name="response_new", methods={"GET", "POST"})
*/
public function new(Request $request, EntityManagerInterface $entityManager, QuestionRepository $questionRepository): Response
{
$questions = $questionRepository->findAll();
// dd($questions);
$response = new QuizResponse();
$form = $this->createForm(ResponseType::class, $response); // problem exists
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($response);
$entityManager->flush();
// $formData = $form->getData();
// dd($formData);
// echo 'this should work';exit;
return $this->redirectToRoute('response_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('response/new.html.twig', [
'response' => $response,
'question' => $questions,
'form' => $form,
]);
}
A ResponseType form:
<?php
namespace App\Form;
use App\Entity\Response;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class ResponseType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Response::class,
]);
}
}
And here is my twig template (_form.html.twig):
{{ form_start(form) }}
<div class="mt-3">
{% for question in question %}
<input type="hidden" name="response[question][]" value="{{ question.id }}">
{% set name = 'response[answer]['~ question.id ~']' %}
{% if question.type == 'checkbox' %}
{% set name = name ~ '[]' %}
{% endif %}
{# {% if question.replies is defined %}#}
<div class="mb-3 mt-3 border">
<h3 class="mb-0" id="question-{{ question.id }}">{{ loop.index }}. {{ question.label }}</h3>
</div>
{% if question.type == 'checkbox' or question.type == 'radio' %}
<div class="answers p-1">
{% for answer in question.replies %}
{% set id = 'answer-' ~ answer.id %}
<label for="{{ id }}">{{ answer.answer }}</label>
<input type="{{ question.type }}" name="{{ name }}" id="{{ id }}">
{% endfor %}
</div>
{% elseif question.type == 'textarea' %}
<textarea name="{{ name }}" aria-labelledby="question-{{ question.id }}" cols="30" rows="5" class="form-control"></textarea>
{% elseif question.type == 'number' %}
<input type="number" name="{{ name }}" aria-labelledby="question-{{ question.id }}">
{% else %}
<input type="text" name="{{ name }}" aria-labelledby="question-{{ question.id }}" class="form-control">
{% endif %}
{# {% endif %}#}
{% endfor %}
</div>
<button class="btn btn-primary">{{ button_label|default('Submit') }}</button>
{#{{ form_end(form, {render_rest: false }) }}#}
{{ form_end(form) }}
It's completely detached from the responseType form which is why I believe it doesn't submit to the database... But I am not sure how to map the ResponseType form to the _form.html.twig
you dont use the form component - you have the class and the correct controller code, but in your template you print the form fields yourself.
Just define the fields in your formType, remove the formfields from your twig and use the supposed {{ form_*() }} methods.
To make this conditional rendering of your CollectionType work - you will have to write some twig code in your form theme. (but to explain this is outside of my time and scope of the question)
I can create a record but when editing it throws me the error
I can also see the log
but not edit it
spl_object_hash(): Argument #1 ($object) must be of type object, string given
twig part of the view
{% extends 'layouts/layoutmodal.html.twig' %}
{% block content %}
<div class="row">
<div class="col-sm">
{{ form_row(form.created_at, { 'label': 'Fecha de Ingreso:' }) }}
</div>
<div class="col-sm">
{{ form_row(form.nombrecliente, { 'label': 'Nombre Cliente' }) }}
</div>
<div class="col-sm">
{{ form_row(form.nombrecomercio, { 'label': 'Nombre Comercio' }) }}
</div>
<div class="col-sm">
{{ form_row(form.telefono) }}
</div>
<div class="col-sm">
{{ form_row(form.email, { 'label': 'Correo Electronico' }) }}
</div>
</div>
<div class="row">
<div class="col-sm">
{{ form_row(form.documento, { 'label': 'Documento: RUC/DNI/CE' }) }}
</div>
<div class="col-sm">
{{ form_row(form.departamento) }}
</div>
<div class="col-sm">
{{ form_row(form.provincia) }}
</div>
<div class="col-sm">
{{ form_row(form.distrito) }}
</div>
Form controller used to edit all the data loaded in the operation
#[Route('ventas/{id}/editar', name: 'ventas/clientes/editar', methods: ['GET', 'POST'])]
public function editar(Request $request, Cliente $cliente): Response
{
$form = $this->createForm(ClienteType::class, $cliente);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('ventas');
}
return $this->render('ventas/clientes/editar.html.twig', [
'cliente' => $cliente,
'form' => $form->createView(),
]);
}
entity has ManyToOne relationship I don't think you have problems there:
since I can add and delete without any problem...
namespace App\Entity\Ventas;
use App\Repository\Ventas\ProvinciaRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=ProvinciaRepository::class)
*/
class Provincia
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $nombre;
/**
* #ORM\ManyToOne(targetEntity=Departamento::class, inversedBy="provincia")
*/
private $departamento;
/**
* #ORM\OneToMany(targetEntity=Distrito::class, mappedBy="provincia")
*/
private $distrito;
public function __toString()
{
return $this->nombre;
}
public function __construct()
{
$this->distrito = new ArrayCollection();
$this->nombre = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNombre(): ?string
{
return $this->nombre;
}
public function setNombre(string $nombre): self
{
$this->nombre = $nombre;
return $this;
}
public function getDepartamento(): ?Departamento
{
return $this->departamento;
}
public function setDepartamento(?Departamento $departamento): self
{
$this->departamento = $departamento;
return $this;
}
/**
* #return Collection|Distrito[]
*/
public function getDistrito(): Collection
{
return $this->distrito;
}
public function addDistrito(Distrito $distrito): self
{
if (!$this->distrito->contains($distrito)) {
$this->distrito[] = $distrito;
$distrito->setProvincia($this);
}
return $this;
}
public function removeDistrito(Distrito $distrito): self
{
if ($this->distrito->removeElement($distrito)) {
// set the owning side to null (unless already changed)
if ($distrito->getProvincia() === $this) {
$distrito->setProvincia(null);
}
}
return $this;
}
}
I'm trying to return a form to update some attributes in my user entity
I'm tying to have a single page app, so I'm creating forms, trying to setup their Update functions in a single controller, and to load the forms in separate twig templates that are all loaded in the main "profile" page.
My entire user controller:
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\ModifyUserType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class UserController extends AbstractController
{
/**
* #Route("/profile", name="profile", methods={"GET"})
*/
public function index(): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
return $this->render('profile.html.twig', [
'controller_name' => 'UserController',
]);
}
/**
* #Route("/profile", name="update", methods={"PUT"})
* #param UserInterface $user
* #param Request $request
* #param EntityManagerInterface $em
* #return Response
*/
public function updateUser(UserInterface $user, Request $request, EntityManagerInterface $em) : Response
{
$userId = $this->getUser()->getId();
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository(User::class)->find($userId);
$form = $this->createForm(ModifyUserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->getData();
$em->persist($user);
$em->flush();
$this->addFlash('success', 'Saved');
return $this->redirectToRoute('profile');
}
return $this->render('modifyUser.html.twig', [
'updateUser' => $form->createView()
]);
}
}
My form type:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ModifyUserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', TextareaType::class)
->add('MotherLanguage', LanguageType::class)
->add('addLanguage', LanguageType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}
The modal where I'm trying to call the form:
<!-- Modify User Modal -->
<div class="modal fade" role="dialog" id="editUserModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Modify profile</h3>
<button class="close" type="button" data-dismiss="modal">×</button>
</div>
{{ form_start(updateUser) }}
<input type="hidden" name="_method" value="PUT">
<div class="modal-body">
<div class="form-group">
<label for="description">Description</label>
{{ form_row(updateUser.description, { 'id': 'description','attr': { 'class' : 'form-control'}}) }}
<label for="addNativeLanguage">Add your native language</label>
{{ form_row(updateUser.MotherLanguage, { 'id': 'addNativeLanguage','attr': { 'class' : 'form-control'}}) }}
</select>
<label for="addLanguage">Add other languages you speak</label>
{{ form_row(updateUser.addLanguage, { 'id': 'addLanguage','attr': { 'class' : 'form-control'}}) }}
</select>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-light">Update</button>
</div>
{{ form_end(updateUser) }}
</div>
</div>
</div>
<!-- /Modify User Modal -->
Update:
After Modifying the Controller to the following:
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\ModifyUserType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class UserController extends AbstractController
{
/**
* #Route("/profile", name="profile", methods={"GET","PUT"})
*/
public function index(UserInterface $user, Request $request, EntityManagerInterface $em) : Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
/*return $this->render('profile.html.twig', [
'controller_name' => 'UserController',
]);*/
$userId = $this->getUser()->getId();
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository(User::class)->find($userId);
$form = $this->createForm(ModifyUserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->getData();
$em->persist($user);
$em->flush();
$this->addFlash('success', 'Article Created! Knowledge is power!');
return $this->redirectToRoute('profile');
}
return $this->render('profile.html.twig', [
'user' => $user,
'updateUser' => $form->createView()
]);
}
}
I'm getting a new error :
Too few arguments to function App\Entity\User::addLanguage(), 0 passed in /home/majd/Projects/Symfony/Back/vendor/symfony/property-access/PropertyAccessor.php on line 422 and exactly 1 expected.
As per #dutycorpse 's comment instead of going for the property, I was going directly for the function responsible for adding languages.
I create two entity. first Posts and second categories. this to entity have many to many relation.
I make a form from posts and add an embed form categories that use ChoiceType.
the form work correctly an I can add as many as categories to each post.
but in database orm create an posts_categories when I look to its data the posts_id is correct and from posts table but I don't know where the categories_id come from ?
I means the real id of categories not save on posts_categories table. where does this id come? and how I can config that real id save on table?
App\Entity\Posts.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostsRepository")
*/
class Posts
{
public $newFormTitle = 'Add New Post';
public $editFormTitle = 'Edit Post';
public $listFormTitle = 'Posts List';
public $adminList = 'postList';
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
*/
private $title;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Categories" , orphanRemoval=true, cascade={"persist"})
*/
private $categories;
public function __construct()
{
$this->categories = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): self
{
$this->content = $content;
return $this;
}
/**
* #return Collection|Categories[]
*/
public function getCategories(): Collection
{
return $this->categories;
}
public function addCategory(Categories $category): self
{
if (!$this->categories->contains($category)) {
$this->categories[] = $category;
}
return $this;
}
public function removeCategory(Categories $category): self
{
if ($this->categories->contains($category)) {
$this->categories->removeElement($category);
}
return $this;
}
}
App\Entity\Categories.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CategoriesRepository")
*/
class Categories
{
public $newFormTitle = 'Add New Post';
public $editFormTitle = 'Edit Post';
public $listFormTitle = 'Categories List';
public $adminList = 'categoriesList';
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
public function __construct()
{
}
public function getId()
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
}
App\Form\PostsType.php
<?php
namespace App\Form;
use App\Entity\Posts;
use App\Form\CategoriesPostsType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class PostsType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', null, [
'attr' => ['autofocus' => true ,'class'=>'form-control'],
'label' => 'label.title',
'label_attr' => ['class'=>'label-control'],
])
->add('content', null, [
'attr' => ['rows' => 8 ,'class'=>'form-control'],
'label' => 'label.content',
'label_attr' => ['class'=>'label-control'],
])
;
/*
$builder->add('categories', CollectionType::class, array(
'entry_type' => CategoryType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
));
*/
$builder->add('categories', CollectionType::class, array(
'entry_type' => CategoriesPostsType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => true,
'label' => false,
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Posts::class,
]);
}
}
App\Form\CategoriesPostsType.php
<?php
namespace App\Form;
use App\Entity\Categories;
use App\Form\Type\CategoryInputType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use App\Repository\CategoriesRepository;
class CategoriesPostsType extends AbstractType
{
private $categories;
public function __construct(CategoriesRepository $category)
{
$categories = $category->findAll();
$choise = array();
foreach ($categories as $key => $value)
//$choise[] = new Categories($categories[$key]->getId();
$choise[$categories[$key]->getName()] = $categories[$key]->getId();
$this->categories = $choise;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', ChoiceType::class, [
'attr' => ['autofocus' => true ,'class'=>'form-detail-control formChoiceType'],
'label' => false,
'label_attr' => ['class'=>'label-control'],
'choices' => $this->categories,
])
/* ->add('category', CategoryInputType::class, [
'label' => 'label.category',
'required' => false,
])*/
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Categories::class,
]);
}
}
twig form
{% extends 'theme/basickTheme/form.html.twig' %}
{% block body_id 'content_new_posts' %}
{% block defaultFormHeader entity.newFormTitle %}
{% block defaultFormBody %}
<div class="row">
<div class="col-12">
{{ form_start(form) }}
<div class="col-12">
{{ form_errors(form) }}
</div>
<div class="col-12">
{{ form_row(form.title) }}
</div>
<div class="col-12">
{{ form_row(form.content) }}
</div>
<div class="col-12 form-group">
<h3>Categories</h3>
</div>
<div class="col-12">
<button type="button" class="btn add-detail-botton" onclick="addCategoriesForm(1)">
<img src="{{ asset('img/custum-icon/add-small.png') }}">
<i class="fa fa-save" aria-hidden="true"></i> {{ button_label|default('categories.label.add.category'|trans) }}
</button>
<ol class="detail-list" data-prototype="{{ form_widget(form.categories.vars.prototype)|e('html_attr') }}">
{# iterate over each existing tag and render its only field: name #}
{% for Category in form.categories %}
<li>{{ form_widget(Category.name) }}</li>
{% endfor %}
</ol>
<button type="button" class="btn add-detail-botton" onclick="addCategoriesForm(2)">
<img src="{{ asset('img/custum-icon/add-small.png') }}">
<i class="fa fa-save" aria-hidden="true"></i> {{ button_label|default('categories.label.add.category'|trans) }}
</button>
</div>
<div class="col-12 form-footer-botton">
<button type="submit" class="btn btn-primary">
<i class="fa fa-save" aria-hidden="true"></i> {{ button_label|default('Label.save'|trans) }}
</button>
<a href="{{ backUrl }}" class="btn btn-primary">
<i class="fa fa-list-alt" aria-hidden="true"></i> {{ 'Label.back.to.list'|trans }}
</a>
</div>
{{ form_end(form) }}
</div>
</div>
{% endblock %}
{% block javascripts %}`
<script>
var $collectionHolder;
jQuery(document).ready(function() {
// Get the ul that holds the collection of tags
$collectionHolder = $('ol.detail-list');
$collectionHolder.find('li').each(function() {
addCategoriesFormDeleteLink($(this));
});
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
});
function addCategoriesForm( $appendType = 1) {
var $collectionHolder = $('ol.detail-list');
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
var newForm = prototype;
// You need this only if you didn't set 'label' => false in your tags field in TaskType
// Replace '__name__label__' in the prototype's HTML to
// instead be a number based on how many items we have
// newForm = newForm.replace(/__name__label__/g, index);
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
newForm = newForm.replace(/__name__/g, index);
newForm = newForm.replace(/form-detail-control/g, 'form-detail-control-new');
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<li class="prototye"></li>').prepend(newForm);
if($appendType == 1)
$collectionHolder.prepend($newFormLi);
else
$collectionHolder.append($newFormLi);
//$newLinkLi.before($newFormLi);
addCategoriesFormDeleteLink($newFormLi);
}
function addCategoriesFormDeleteLink($tagFormLi) {
//var $removeFormButton = $('<button type="button" class="detailDeleteButton">Delete</button>');
var $removeFormButton = $('<button type="button" class="btn delte-detail-botton"><img src="{{ asset('img/custum-icon/delete-smal.png') }}"><i class="fa fa-save" aria-hidden="true"></i>{{ button_label|default('categories.label.delete.category'|trans) }}</button>');
$tagFormLi.append($removeFormButton);
$removeFormButton.on('click', function(e) {
// remove the li for the tag form
$tagFormLi.remove();
});
}
</script>
{% endblock %}
I was trying to create a custom form type with it's own template view via Symfony 4 Documentation but I got a lot of errors by searching and trying to create one.
Here is my custom form type file ImageChoiceType.php:
<?php
namespace App\Form\Type;
use App\Entity\Media;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ImageChoiceType extends AbstractType
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
// 'data_class' => Media::class,
'class' => Media::class,
'choices' => $this->entityManager->getRepository(Media::class)
->findAll(),
));
}
public function getParent()
{
return EntityType::class;
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'image_choice';
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'image_choice';
}
}
Here is my field template:
{% block image_choice_widget %}
<div class="image_widget">
<div class="radio">
<label for="post_image_placeholder" class="">
<input type="radio" id="post_image_placeholder" name="post[image]" value=""
{{ form.vars.value == "" ? "checked='checked'" : ""}}> None
</label>
</div>
<div class="image-container">
{% for key,choice in form.vars.choices %}
<div class="radio col-xs-2">
<label for="post_image_{{ key }}" class="">
<input class="image-radio-buttons" type="radio" id="post_image_{{ key }}" name="post[image]"
value="{{ key }}" {{ form.vars.value == key ? "checked='checked'" : ""}}>
<img src="{{ asset(constant('App\\Constants::UPLOAD_MEDIA')~choice.label) }}" class="image-thumbs img-responsive"/>
</label>
</div>
{% endfor %}
<div class="clearfix"></div>
</div>
</div>
{% endblock %}
The interesting part is if I override one of the built-in types of Symfony via this template by changing the first line to {% block entity_widget %} and use EntityType in my form builder, it works well. But as I started to populate this with my own custom type, It got angry and showed a lot of unrelated errors!
Any help or instruction?
OK, I found out how to create it!
That was so easy. The documentation showed that it's so complex, but actually it's not.
This is the custom form type file ImageChoiceType.php:
<?php
namespace App\Form\Type;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ImageChoiceType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
}
public function getParent()
{
return EntityType::class;
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'image_choice';
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'image_choice';
}
}
This is how I use this field in my form builder:
...
->add('image',ImageChoiceType::class , [
'label' => 'Choose an Image',
'class' => Media::class
])
...
and the template that I've provided in the question is exactly what generates the pictures!