I am creating a library management system in Symfony to progress.
So I have a book, user, category entity.
So I want to create the borrow a book function.
However, when I perform the action, the book does not change in the database (book must become borrowed).
So here is my controller (knowing that it is a user who borrows, and the user can addBook as there is a join):
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Entity\Book;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
class BookController extends AbstractController
{
//Home page of Book, to render all the books
#[Route('/book', name: 'app_book')]
public function index(ManagerRegistry $doctrine): Response
{
$books = $doctrine->getRepository(Book::class)->findAll();
return $this->render('book/index.html.twig', [
'controller_name' => 'BookController',
'books' => $books,
]);
}
// Show a book
#[Route("/book/{id}", name: 'book_show')]
public function show(Book $article, EntityManagerInterface $manager , Request $request, $id){
$repo = $manager->getRepository(Book::class);
$book = $repo->find($id);
return $this->render('book/show.html.twig', [
'book' => $book,
]);
}
// Display the borrow form
#[Route("/book/borrow/{id}", name: 'borrow_book')]
public function RenderBorrow(Book $book, EntityManagerInterface $manager, $id){
$repo = $manager->getRepository(Book::class);
$book = $repo->find($id);
return $this->render('book/borrow.html.twig', [
'book' => $book,
]);
}
// method to actually borrow the book, then redirect to the book page
#[Route("/book/borrowed/{id}", name: 'book_borrowed')]
public function borrow(Book $book, EntityManagerInterface $manager, $id){
$repo = $manager->getRepository(Book::class);
$book = $repo->find($id);
// return true or false if book is borrowed or not
$availabilty = $book->isBorrowed();
// if available, then the user can borrow it.
if($availabilty){
// we get the user.
/** #var \App\Entity\User $user */
$user = $this->getUser();
$user->addBook($book);
// the book borrow becomes borrowed.
$book->setBorrowed(true);
$manager->persist($book);
$manager->flush();
$manager->persist($user);
$manager->flush();
}
return $this->redirectToRoute('book_show', ['id' => $book->getId()]);
}
}
And my twig borrow.html.twig
{% extends 'base2.html.twig' %}
{% block title %} Emprunter {{ book.title}} {% endblock %}
{% block body %}
<!-- Page to borrow a specified book
-->
<!-- If book is borrowed, then we display yes, or else no. If no, we can display a
button to borrow it -->
<section class="articles">
<form action="{{ path('book_borrowed', {'id' : book.id })}}" method="post">
{% if not book.borrowed %}
<h5> Emprunter </h5>
<h6> Nom du livre : {{ book.title}}</h6>
<h6> Catégorie du livre : {{ book.categorie}}</h6>
<button type="submit" class="btn btn-primary"> Emprunter </button>
</form>
{% else %}
<div class="alert alert-dismissible alert-danger">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Oh !</strong> <a href="#" class="alert-link">Vous tentez d'emprunter un livre déjà emprunté !
</div>
{% endif %}
</section>
{% endblock %}
Please help me, because normally afterwards the book should update and become borrowed...
You are updating the borrowed boolean when availability is equal to true.
What you want to do is check if the book is not borrowed, and if it's not update the value of your boolean.
You do not need to persist $book and $user since they are already persisted. Also you only need to flush once at the end.
Moreover, Book $book should give you the proper $book depending on the id, without having to look it up yourself in the repository
You could replace your method with:
// method to actually borrow the book, then redirect to the book page
#[Route("/book/borrowed/{id}", name: 'book_borrowed')]
public function borrow(Book $book, EntityManagerInterface $manager, $id){
// if available, then the user can borrow it.
if(!$book->isBorrowed()){
// we get the user.
/** #var \App\Entity\User $user */
$user = $this->getUser();
$user->addBook($book);
// the book borrow becomes borrowed.
$book->setBorrowed(true);
$manager->flush();
}
return $this->redirectToRoute('book_show', ['id' => $book->getId()]);
}
Related
Im learning Symfony and I'm creating a CRUD app for practicing.
I want to implement a search function in the page where I list my db items. I was wondering what is the correct way to achieve this.
Right now, I have created a searchType and searchController with the next code:
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class searchController extends Controller
{
public function searchAction(){
$formulario = $this->createForm('AppBundle\Form\SearchType');
return $this->render('searchBar.html.twig', ['form' => $formulario->createView()]);
}
}
class SearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('key', ChoiceType::class,
['choices' => [
'Elegir Campo...' => 0,
'Modelo' => 1,
'Marca' => 2,
'Año' => 3,
'Propietario' => 4
]
])
->add('term', TextType::class)
->add('buscar', SubmitType::class)
;
}
}
I have another controller called itemController, where i have the list, add, modify and delete actions. With the twig render() function, I'm rendering the searchBar in the items list page. Which is the correct way to get the values from the 'key' and the 'term' elements and use them to make queries against the db?
I have tried to achieve this without the searchController/searchType and I used a simple <form> in the template and got the key and term values with $request->get() method in the listAction. After I created a switch-case statement to execute queries according to the key value. I could achieve what i wanted like this, but I want to be able to do this the correct way.
Can someone help me/give me some hints on how to continue from this?
Thanks.
Update:
Items Controller:
/**
*#Route("/items", name="items")
*/
public function listAction(Request $request){
$em = $this->getDoctrine()->getManager();
$items = $em->getRepository('AppBundle:Item')->findAll();
return $this->render('items.html.twig', ['items' => $items]);
}
My items.html.twig:
{% extends base.html.twig %}
{% block body %}
{{ render(controller('AppBundle:search:search')) }}
...
{% endblock %}
My searchBar.html.twig:
{{ form_start(form, {'attr': {'class': 'form-inline float-left my-2 my-lg-0'}}) }}
{{ form_widget(form.key) }}
{{ form_widget(form.term, {'attr': {'class': 'ml-1'}}) }}
{{ form_widget(form.buscar, {'attr': {'class': 'btn btn-outline-success ml-1'}}) }}
{{ form_end(form) }}
What i tried with routing and works with the searchController:
/**
* #Route("/search", name="search")
*/
public function searchAction(Request $request){
$em = $this->getDoctrine()->getManager();
$formulario = $this->createForm('AppBundle\Form\SearchType');
$formulario->handleRequest($request);
if($formulario->isSubmitted() && $formulario->isValid()){
$data = $formulario->getData();
$key = $data["key"];
$term = $data["term"];
$items = $em->getRepository('Item::class')->findByTerm($key, $term);
return $this->render('items.html.twig', ['items' => $items]);
}
return $this->render('searchBar.html.twig', ['form' => $formulario->createView()]);
}
If i go to /search and search for an item, it redirects me to my items page with the item i searched. But, If i use the search bar in my items page that i rendered using {{ render(controller('AppBundle:search:search')) }}, it doesn't work.
You are not very far from reaching your goal.
On your Controller, you can update your code to process the incoming request:
public function searchAction(Request $request){
$formulario = $this->createForm('AppBundle\Form\SearchType');
$formulario->handleRequest($request);
if ($formulario->isSubmitted() && $formulario->isValid()) {
$data = $formulario->getData();
// ... perform some action, such as saving the data to the database or search
}
return $this->render('searchBar.html.twig', ['form' => $formulario->createView()]);
}
You can find here more information about Processing Forms
To search into your database, you can process your repositories corresponding to the data. For more information about that, you can go here on Symfony Doctrine documentation
To go further, there is a bundle allowing simplified management of search forms: Lexik Form Filter Bundle
I am learning Symfony so I am testing various ways to work with database. The idea is to make select dropdown list that contains data from table in database. Each option in select returns value that is id of column from table. Dropdown list is defined in View in this way ('find_res' in action is route to controller):
<form action="{{ path('find_res') }}" method="POST" name="formpt">
<select name="patientId">
{% for patient in patients %}
{{dump(patient)}}
<option value="{{patient.id}}">{{patient.lastname}}{{patient.firstname}}</option><br />
{% endfor %}
</select>
<button type="submit" name="submit">Find results</button>
</form>
so that for chosen patient.id it should show results in table which is defined in code bellow this as standard html table.
In controller method I created list of all patients from table in database and sent it as parameter to this view. And that works just fine. Also, I would not get into details how query is built because the problem is that any $request I try does not return anything, just null. I tried:
$request->request->get('patientId'); //returns null always
$request->get('patientId'); //also null
I even tried to give name to form in view like for instance 'formpt' and then tried it with:
$request->request->get('formpt')['patientId'];
still nothing. This test code:
if($request->getMethod() == 'POST' ) {
\Doctrine\Common\Util\Debug::dump($request->request->get('patientId'));
exit;
}
always returns NULL or string(0) "" for whatever I put in "dump".
What I am doing wrong?
Thanks!
The following is a simple way to implement what you're trying to do with the standard form component (note that I explicitly didn't create a dedicated form type!).
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Constraints\NotBlank;
use AppBundle\Entity\Patient;
class DefaultController extends Controller
{
/**
* #Route("/", name="index")
*
* #param Request $request
*
* #return Response
*/
public function indexAction(Request $request)
{
$form = $this->createFormBuilder()
->add('patient', EntityType::class, [
'required' => false,
'placeholder' => '--- Select ---',
'class' => Patient::class,
'choice_label' => function (Patient $patient) {
return sprintf(
'%s, %s',
$patient->getLastname(),
$patient->getFirstname()
);
},
'constraints' => [
new NotBlank()
],
])
->getForm()
->handleRequest($request)
;
if ($form->isSubmitted() && $form->isValid()) {
/** #var $selected Patient */
$selected = $form->get('patient')->getData();
dump($selected);
}
return $this->render('default/index.html.twig', [
'form' => $form->createView()
]);
}
}
the view being:
{% extends 'base.html.twig' %}
{% block body %}
{{ form_start(form) }}
{{ form_widget(form) }}
<button>Find results</button>
{{ form_end(form) }}
{% endblock %}
I understand that the form component (especially form customization) has it's learning curve, but ultimately, if you want to use symfony fully you'll end up using it anyway (why reinvent the wheel). So the sooner you start with it the better.
Maybe try this
For mapped field
public function yourAction(Request $request){
$patient = new Patient();
$form = $this->createForm('AppBundle\Form\PatientType', $patient);
$form->handleRequest($request);
if ($form->isSubmitted())
{
$patientId = $patient->getId();
}
}
If you need to get a non mapped field (field that's not a Patient property)
public function yourAction(Request $request){
$patient = new Patient();
$form = $this->createForm('AppBundle\Form\PatientType', $patient);
$form->handleRequest($request);
if ($form->isSubmitted())
{
$patientCustomField = $form['customField']->getData();
}
}
I have my entity Article and one single table inheritance like this :
/**
* Article
*
* #ORM\Table(name="article")
* #ORM\Entity(repositoryClass="PM\PlatformBundle\Repository\ArticleRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="media", type="string")
* #ORM\DiscriminatorMap({"article" = "Article", "movie" = "Movie", "image" = "Image", "text" = "Text"})
*/
class Article
{
protected $id;
protected $title;
protected $description;
protected $author;
//other attributes and setters getters
}
class Image extends Article
{
private $path;
//getter setter
}
class Movie extends Article
{
private $url;
//getter setter
}
So my article's object type is either Image or movie or text only. Ok now I would like build a form wherein users can post a new article : in this form, the user has to choice between tree type (3 radios button) : image OR movie OR text only and of course the other fields : title and description. How I can do that ? Because with the command
php bin/console doctrine:generate:form myBundle:Article
The form rendered is :
class ArticleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class)
->add('description', TextareaType::class)
->add('save', SubmitType::class);
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PM\PlatformBundle\Entity\Article'
));
}
}
I don't know the way to implement my STI relation in this form. Because I have not field in my Article entity/object for the type (only in my table).
I have to add a Custom ChoiceType() field but it require a attribute.
When I try to add this in the form :
->add('path', SearchType::class)
->add('url', UrlType::class)
I got this error :
Neither the property "path" nor one of the methods "getPath()", "path()", "isPath()", "hasPath()", "__get()" exist and have public access in class "PM\PlatformBundle\Entity\Article".
Because I have create an instance of Article, not an instance of Image or Movie. Initially I created a STI thinking a new instance of Article would allow me also to define the "type" of article. But not ? Right ?
You will have to make three forms (one for an Article, one for a Movie and one for an Image). Then, in your controller, you have to options to deal with them:
Either you use one action to handle the three forms (you can check wich one is submitted by using $form->isSubmitted())
You create one action by form, and set the form action URL for each form to the correct controller.
Finally, in your template, you encapsulate your forms in a div, and use the example in my previous post.
{% extends "CoreBundle::layout.html.twig" %}
{% block title %}{{ parent() }}{% endblock %}
{% block btn_scrollspy %}
{% endblock %}
{% block bundle_body %}
<div class="well">
<div class="selector">
<input type="radio" name="form-selector" value="article-form"> Article
<input type="radio" name="form-selector" value="movie-form"> Movie
<input type="radio" name="form-selector" value="image-form"> Image
</div>
<div class="form article-form" style="display: none;">
{{ form(articleForm) }}
</div>
<div class="form movie-form" style="display: none;">
{{ form(movieForm) }}
</div>
<div class="form image-form" style="display: none;">
{{ form(imageForm) }}
</div>
</div>
{% endblock %}
I agree with dragoste in the comments: you can't expect the form to deduce by himself the type of class you want to instantiate based on a value.
Roughly, Image and Movie are the same type as Article, but an Article is not an Image and/or a Movie.
You will have to check that manually. You can do that server side like explained in the comments, with a field using mapped: false to determine the type of entity you need to instantiate, or client side with javascript by using three forms (one for a movie, one for an article, one for an image) and by displaying the correct one based on your radio button.
Edit: How to display the correct form in JS?
I created a JSFiddle to show you how you can do this using jQuery : https://jsfiddle.net/61gc6v16/
With jQuery documentation, you should be able to quickly understand what this sample do, and to adapt it to your needs. :)
#Boulzy I did this and it works:
class ArticleController extends Controller
{
public function addAction(Request $request)
{
$form1 = $this->get('form.factory')->create(TextOnlyType::class);
$form2 = $this->get('form.factory')->create(ImageType::class);
$form3 = $this->get('form.factory')->create(MovieType::class);
return $this->render('PMPlatformBundle:Article:add.html.twig', array(
'ArticleTextform' => $form1->createView(),
'ArticleImageform' => $form2->createView(),
'ArticleMovieform' => $form3->createView(),
));
}
public function addArticleTextAction(Request $request)
{
$ArticleText = new TextOnly;
$form = $this->get('form.factory')->create(TextOnlyType::class, $ArticleText);
if ($request->isMethod('POST') && $form->handleRequest($request)->isValid()) {
$ArticleText->setAuthor(5);
$ArticleText->setLikes(0);
$em = $this->getDoctrine()->getManager();
$em->persist($ArticleText);
$em->flush();
$request->getSession()->getFlashBag()->add('notice', 'Annonce bien enregistrée.');
$listComments = $ArticleText->getComments();
return $this->render('PMPlatformBundle:Article:view.html.twig', array(
'article' => $ArticleText,
'listComments' => $listComments
));
}
return $this->render('PMPlatformBundle:Article:add.html.twig', array(
'form' => $form->createView(),
));
}
public function addArticleImageAction(Request $request)
{
//the same system as TextOnly
}
public function addArticleMovieAction(Request $request)
{
//the same system as TextOnly
}
}
(I override action directly in my template. With POST method.)
addAction is my contoller which is called by route for display the view of three forms. As you can see this code works as long as there is no error on submitted form. Because, in this case (when error occured when a form is submitted) each controller needs to return the initial view with the 3 forms. How I can do that ?
In my case i have 30 entities that there are no relation between them and every entities have two columns. I wanted to have 6 or 7 entities in the one form, but i don't know what is the best way to do it? this is my code ...
this is my Controller:
public function general1Action(Request $request)
{
$example1 = new Example1();
$example2 = new Example2();
$example3 = new Example3();
$example4 = new Example4();
$formexample1 = $this->createForm('...Bundle\Form\Example1Type', $example1);
$formexample2 = $this->createForm('...Bundle\Form\ Example2Type', $example2);
$formexample3 = $this->createForm('...Bundle\Form\ Example3Type', $example3);
$fprmexample4 = $this->createForm('...Bundle\Form\Example4Type', $example4);
$example1->handleRequest($request);
$example2->handleRequest($request);
$example3->handleRequest($request);
$example4->handleRequest($request);
$em = $this->getDoctrine()->getManager();
if ($example1->isSubmitted() && $example1->isValid()) {
/**
* To generate the value example1 first column(e1fc) and second column(e1sc)
*/
$prefix=$this->container->getParameter('prefix');
$e1fcCle=$em->getRepository("...Bundle:Example1")->genereCle('Example1',$prefix);
$example1->sete1fc($e1fcCle);
/**
* To generate the value example1 second column(e1sc)
*/
$example1->sete1sc("e1sc".($e1fcCle));
/**
* to check, the fields are not empty
*/
if(($formexample2["e2sc"]->getData())!=""){
$example2->sete1fc($e1fcCle);
$em->persist($example2);
}
if(($formexample3["e3sc"]->getData())!=""){
$example3->sete1fc($e1fcCle);
$em->persist($example3);
}
if(($formexample4["e4sc"]->getData())!=""){
$example4-> sete1fc($e1fcCle);
$em->persist($example4);
}
$em->persist($example1);
$em->flush();
return $this->forward('...Bundle:General...: general2',
array('E1FC' => $e1fcCle));
}
return $this->render('.../general1.html.twig', array(
'example1' => $example1,
'formExample1' => $formexample1->createView(),
'formExample2' => $formexample2->createView(),
'formExample3' => $formexample3->createView(),
'formExample4' => $formexample4->createView(),
));
}`
and this is my general1.html.twig :
{% extends 'base.html.twig' %}
{% block body %}
{{ form_start(formExample1) }}
{{ form_row(formExample2.e2sc) }}
{{ form_row(formExample3.e3sc) }}
{{ form_row(formExample4.e4sc) }}
<input type="submit" value="Next" />
{{ form_widget(formExample1._token) }}
{{ form_end(formExample1, {"render_rest":false}) }}
<ul>
<li>
Back to the list
</li>
</ul>
{% endblock %}`,
I have another question : when i try to use isValid() for the other form like this
if(($formexample2["e2sc"]->getData())!=""&& $example2->isValid())
i have this error: Fatal error: Call to a member function get...() on null
I'm building a page in Symfony2 that permits me to search a term (e.g. a name of a person) in my Database and show in the same page (even after a page reload) all matching records (e.g. all the person with this name).
This is my anagrafica.html.twig
{# src/Acme/MyBundle/Resources/views/Page/anagrafica.html.twig #}
{% extends 'AcmeMyBundle::layout.html.twig' %}
{% block body %}
<form id="formsearch" name="p" method="get" action="anagrafica">
<span>
<input type="text" name="search_name" id="search_name" />
</span>
<input type="image" name="button_search" />
</form>
{% for anagrafica in anagrafiche %}
<article class="blog">
<div class="date">{{ anagrafica.DataNascita|date('c') }}</div>
<header>
<h2>{{ anagrafica.nome }}</h2>
</header>
<div class="snippet">
<p>{{ anagrafica.cognome }}</p>
<p class="show_complete">Show all data</p>
</div>
</article>
{% else %}
<p>There are no entries</p>
{% endfor %}
{% endblock %}
This is my PageController.php
<?php // src/Acme/MyBundle/Controller/PageController.php
namespace Acme\MyBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class PageController extends Controller {
//...
public function anagraficaAction() {
$em = $this->getDoctrine()
->getEntityManager();
$anagrafiche = $em->createQueryBuilder()
->select('b')
->from('AcmeMyBundle:Anagrafiche', 'b')
->where("b.nome = :nome")
->setParameter('nome', 'Alex' )
->addOrderBy('b.id', 'DESC')
->getQuery()
->getResult();
return $this->render('AcmeMyBundle:Page:anagrafica.html.twig', array('anagrafiche' => $anagrafiche));
}
}
I think I only need to update my PageController.php and replace the name 'Alex' in :
->setParameter('nome', 'Alex' )
with a variable that refers to the entry in my form definied in anagrafica.html.twig.
Anyway I have no idea of how to do this, and a quick search on google and forums do not helped me.
Any suggestion?
You get GET-parameters via
$searchName = $request->query->get('search_name');
But for this you still need the $request variable. You can use it as parameter and change your method signature to this:
public function anagraficaAction(Request $request)
This way, you can call the $request parameter in your method.
The other way is to get the request of the current controller inside your method.
$request = $this->get('request');
Using this you can change your setParameter to this:
setParameter('nome', $searchName)
change this setParameter('nome', 'Alex') to something like setParameter('nome', $_GET['search_name']) since this form passing data by using get method.
I found the solution. I edited my PageController.php such this:
<?php
// src/Acme/MyBundle/Controller/PageController.php
namespace Acme\MyBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class PageController extends Controller
{
public function indexAction()
{
return $this->render('AcmeMyBundle:Page:index.html.twig');
}
public function infoAction()
{
return $this->render('AcmeMyBundle:Page:info.html.twig');
}
public function anagraficaAction(Request $request)
{
$em = $this->getDoctrine()
->getEntityManager();
$title = $request->get('search_name');
$anagrafiche = $em->createQueryBuilder()
->select('b')
->from('AcmeMyBundle:Anagrafiche', 'b')
->where("b.nome = :nome")
->setParameter('nome', $title )
->addOrderBy('b.id', 'DESC')
->getQuery()
->getResult();
return $this->render('AcmeMyBundle:Page:anagrafica.html.twig', array('anagrafiche' => $anagrafiche));
}
}