Symfony handle form request logic - symfony

Sorry if this question is too messy, but is there a common way to handle form request in Symfony ? (I use SF 4).
For now, I have the logic in my controller :
$formBooking = $this->createForm(BookingType::class);
$formBooking->handleRequest($request);
if ($formBooking->isSubmitted() && $formBooking->isValid()) {
// perform actions ...
I have several forms on the same page, so my controller gets bigger and bigger.
I wanted to creation a new folder like Action and put the logical here.
And do in my controller :
$formBooking = $this->createForm(BookingType::class);
// $bookingAction = new App\Action\BookingAction
$bookingAction->handleRequest($formBooking, $request);
I just want to know if there any "official" way for this ?

You can create an abstract "BaseController", and inherit your other controller. I don't think this is an "official" way, but it seems proper.
In my example, I have set my entity manager, my form factory and my routing as services. Also, it's called BaseManager, as all my logic are in manager file, while my controller doesn't have code (kind of just calling my manager, except for specific cases)
To do so, simply reference your service, and let your controller be the master, but a clean and simple one.
## Controller##
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Category;
use AppBundle\Form\CategoryType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
/**
* #Route("/admin/category")
*/
class CategoryController extends Controller
{
/**
* #Template()
* #Route("/", name="category_index")
* #return array
*/
public function indexAction()
{
$categories = $this->get('app.category.manager')->find();
return array('categories' => $categories);
}
/**
* #Template()
* #Route("/", name="category_menu")
* #return array
*/
public function menuAction()
{
$categories = $this->getDoctrine()->getRepository('AppBundle:Category')->findAllOrdered("ASC");
return array('categories' => $categories);
}
/**
* #Template()
* #Route("/icones/glyficones", name="category_icones_glyficones")
* #return array
*/
public function fontawesomeAction()
{
return array();
}
/**
* #Template()
* #Route("/icones/fontawesome", name="category_icones_fontawesome")
* #return array
*/
public function glyficoneAction()
{
return array();
}
/**
* #Template()
* #Route("/new", name="category_create")
* #param Request $request
* #return array
*/
public function newAction(Request $request)
{
return $this->get('app.category.manager')->save($request);
}
/**
* #Template()
* #Route("/{id}/edit", name="category_edit")
* #param Request $request
* #param $id
* #return array
*/
public function editAction(Request $request, $id)
{
return $this->get('app.category.manager')->edit($request, $id);
}
/**
* #Template()
* #Route("/{id}/delete", name="category_delete")
* #param Request $request
* #param $id
* #return array
*/
public function deleteAction(Request $request, $id)
{
return $this->get('app.category.manager')->delete($request, $id);
}
}
Base Manager
<?php
namespace AppBundle\Manager;
use AppBundle\Form\CategoryType;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
abstract class BaseManager
{
protected $em;
protected $formFactory;
protected $router;
/**
* CategoryManager constructor.
* #param $em
* #param $formFactory
* #param Router $router
*/
public function __construct($em, $formFactory, Router $router)
{
$this->em = $em;
$this->formFactory = $formFactory;
$this->router = $router;
}
/**
* #param $entity
*/
protected function persistAndFlush($entity)
{
$this->em->persist($entity);
$this->em->flush();
}
/**
* #param $entity
*/
protected function removeAndFlush($entity)
{
$this->em->remove($entity);
$this->em->flush();
}
/**
* #param Request $request
* #param Form $form
* #param $entity
* #param $path
* #return array|RedirectResponse
*/
protected function handleBaseForm(Request $request, Form $form, $entity, $path)
{
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->persistAndFlush($entity);
return new RedirectResponse($this->router->generate($path));
}
return array('form' => $form->createView());
}
/**
* #param $route
* #return RedirectResponse
*/
protected function redirect($route)
{
return new RedirectResponse($this->router->generate($route));
}
}
And here an example, a simple crud froma "Category" entity.
Specific Entity Manager
<?php
namespace AppBundle\Manager;
use AppBundle\Entity\Category;
use AppBundle\Form\CategoryType;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Class CategoryManager
* #package AppBundle\Manager
*/
class CategoryManager extends BaseManager
{
/**
* CategoryManager constructor.
* #param $em
* #param $formFactory
* #param Router $router
*/
public function __construct($em, $formFactory, Router $router)
{
parent::__construct($em, $formFactory, $router);
}
/**
* #return mixed
*/
public function find()
{
return $this->em->getRepository('AppBundle:Category')->findAll();
}
/**
* #param Request $request
* #return array
*/
public function save(Request $request)
{
$category = new Category();
return $this->handleForm($request, $category);
}
/**
* #param Request $request
* #param $id
* #return array|RedirectResponse
*/
public function edit(Request $request, $id)
{
$category = $this->em->getRepository('AppBundle:Category')->find($id);
return $this->handleForm($request, $category);
}
/**
* #param Request $request
* #param $id
* #return RedirectResponse
*/
public function delete(Request $request, $id)
{
$category = $this->em->getRepository('AppBundle:Category')->find($id);
$this->persistAndFlush($category);
return $this->redirect('category_index');
}
/**
* #param Request $request
* #param Category $category
* #return array|RedirectResponse
*/
public function handleForm(Request $request, $category)
{
$form = $this->formFactory->create(CategoryType::class, $category);
return $this->handleBaseForm($request, $form, $category, "category_index");
}
}
Just if needed, here are my services.yaml file
services:
app.category.manager:
class: AppBundle\Manager\CategoryManager
arguments: [ '#doctrine.orm.entity_manager', '#form.factory', '#router' ]
fos_user.doctrine_registry:
alias: doctrine
Hope it may help solving your issue, and get a cleaner code. Please, note that this is a personnal solution, and there are probably better solutions. But this one is a working one, which seems, for me, cleaner than just let the controller have all the logic

Related

Unable to login via PHPUnit Tests with Symfony 4

I'm trying to run a simple auth test using PHPUnit with Symfony 4 to see if the user has successfully logged in. I followed the documentation for running a auth test. The /admin page will automatically redirect to /login if the user is not signed in.
I'm getting the this error on running the test:
There was 1 failure:
1) App\Tests\AuthTest::testAdminPanel
Failed asserting that 302 is identical to 200.
~config/packages/test/security.yaml:
security:
firewalls:
main:
http_basic: ~
Test:
public function testAdminPanel() {
$client = static::createClient(array(), array(
'PHP_AUTH_USER' => 'username',
'PHP_AUTH_PW' => 'password',
));
$crawler = $client->request('GET', '/admin');
$this->assertSame(Response::HTTP_OK, $client->getResponse()->getStatusCode());
}
Note: The username account along with the password, password, do exist in the database.
Security Controller:
<?php
namespace App\Security;
use App\Form\LoginFormType;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
/**
* Class LoginFormAuthenticator
* #package App\Security
*/
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
/**
* #var FormFactoryInterface
*/
private $formFactory;
/**
* #var EntityManager
*/
private $em;
/**
* #var RouterInterface
*/
private $router;
/**
* #var UserPasswordEncoder
*/
private $userPasswordEncoder;
/**
* LoginFormAuthenticator constructor.
* #param FormFactoryInterface $formFactory
* #param EntityManagerInterface $em
* #param RouterInterface $router
* #param UserPasswordEncoderInterface $userPasswordEncoder
*/
public function __construct(FormFactoryInterface $formFactory, EntityManagerInterface $em, RouterInterface $router, UserPasswordEncoderInterface $userPasswordEncoder)
{
$this->formFactory = $formFactory;
$this->em = $em;
$this->router = $router;
$this->userPasswordEncoder = $userPasswordEncoder;
}
/**
* #return string
*/
protected function getLoginUrl()
{
return $this->router->generate('login');
}
/**
* #param Request $request
* #return bool
*
* Called on every request to decide if this authenticator should be
* used for the request. Returning false will cause this authenticator
* to be skipped. Current implementation checks that this request method is POST and
* that the user is on the login page.
*
*/
public function supports(Request $request)
{
return ($request->attributes->get('_route') === 'login' && $request->isMethod('POST'));
}
/**
* #param Request $request
* #param AuthenticationException|null $authException
* #return RedirectResponse|Response
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse($this->router->generate('login'));
}
/**
* #param Request $request
* #return bool|mixed
*/
public function getCredentials(Request $request)
{
$form = $this->formFactory->create(LoginFormType::class);
$form->handleRequest($request);
$data = $form->getData();
$request->getSession()->set(
Security::LAST_USERNAME,
$data['username']
);
return $data;
}
/**
* #param mixed $credentials
* #param UserProviderInterface $userProvider
* #return null|object|UserInterface
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials['username'];
$user = $this->em->getRepository('App:User')->findOneBy(['username' => $username]);
if(empty($user)) {
$user = $this->em->getRepository('App:User')->findOneBy(array('email' => $username));
}
return $user;
}
/**
* #param mixed $credentials
* #param UserInterface $user
* #return bool
*/
public function checkCredentials($credentials, UserInterface $user)
{
$password = $credentials['password'];
if($this->userPasswordEncoder->isPasswordValid($user, $password)) {
return true;
}
return false;
}
/**
* #return string
*/
public function getDefaultSuccessRedirectUrl()
{
return $this->router->generate('default');
}
/**
* #param Request $request
* #param AuthenticationException $exception
* #return RedirectResponse
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
return new RedirectResponse($this->router->generate('login'));
}
/**
* #param Request $request
* #param TokenInterface $token
* #param string $providerKey
* #return null|RedirectResponse|Response
* #throws \Doctrine\ORM\ORMException
* #throws \Doctrine\ORM\OptimisticLockException
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$user = $token->getUser();
$user->setLastLogin(new \DateTime('now'));
$this->em->persist($user);
$this->em->flush();
return new RedirectResponse($this->router->generate('easyadmin'));
}
/**
* #return bool
*/
public function supportsRememberMe()
{
return false;
}
}
You are using AbstractGuardAuthenticator class to authenticate your users. Following the link you provided, you have to use PostAuthenticationGuardToken class.
public function testAdminPanel(): void
{
// Create client
$client = static::createClient();
// Get user
$user = ...; // The user entity
// Login the user
$this->simulateLogin($user);
// Perform request
$crawler = $client->request('GET', '/admin');
// Assert
$this->assertSame(Response::HTTP_OK, $client->getResponse()->getStatusCode());
}
private function simulateLogin(User $user): void
{
// Get session
$session = self::$container->get('session');
// Set firewall
$firewall = '...'; // Your firewall name
// Authenticate the user
$token = new PostAuthenticationGuardToken($user, $firewall, $user->getRoles());
$session->set('_security_' . $firewall, serialize($token));
$session->save();
// Set cookie
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}
To be able to use this method, you can create a fixture to create the user in your test database. See Doctrine Fixture Bundle and even use Liip Test Fixture Bundle to be able to load the fixture and truncate the database at each functional test.
I am using Symfony 5, but I guess it will not be very different.
Hope it will help !
your test seems fine. HTTP 302 means redirect, we are usually redirected after a successfull login.
try to add $client->followRedirects();
as suggested at https://symfony.com/doc/current/testing#redirecting

Symfony 3.4: Issues on saving an Object ArrayCollection OneToMany

I need your help on my problem for saving my data in table.
I use fosUserBundle and have different user types (Admin, Pro, Client) that are classes extending a base User entity.
I also have a Language entity linked to the User with a one-to-many association.
To subscribe, a userClient needs to check by checkbox the language(s) that he speaks.
The form page is ok, it displays the UserClient form (+ the parent User form) with the list of languages as checkboxes.
The problem is the data saved in table.
I have the same values in the language column:
O:43:"Doctrine\Common\Collections\ArrayCollection":1:{s:53:"Doctrine\Common\Collections\ArrayCollectionelements";a:2:{i:0;O:32:"LanguagesBundle\Entity\Languages":10:{s:36:"LanguagesBundle\Entity\Languagesid";i:2;s:38:"LanguagesBundle\Entity\Languagesname";s:7:"English";s:44:"LanguagesBundle\Entity\Languagesshort_slug";s:2:"en";s:43:"LanguagesBundle\Entity\Languageslong_slug";s:5:"en_EN";s:43:"LanguagesBundle\Entity\LanguagesisDefault";b:0;s:43:"LanguagesBundle\Entity\LanguagesupdatedAt";O:8:"DateTime":3:{s:4:"date";s:26:"2018-04-20 22:46:48.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:12:"Europe/Paris";}s:43:"LanguagesBundle\Entity\LanguagesimageFile";N;s:39:"LanguagesBundle\Entity\Languagesimage";s:39:"Flag_of_United_Kingdom_-_Circle-512.png";s:43:"LanguagesBundle\Entity\LanguagesimageSize";i:37621;s:44:"LanguagesBundle\Entity\LanguagesuserClient";N;}i:1;O:32:"LanguagesBundle\Entity\Languages":10:{s:36:"LanguagesBundle\Entity\Languagesid";i:1;s:38:"LanguagesBundle\Entity\Languagesname";s:9:"Français";s:44:"LanguagesBundle\Entity\Languagesshort_slug";s:2:"fr";s:43:"LanguagesBundle\Entity\Languageslong_slug";s:5:"fr_FR";s:43:"LanguagesBundle\Entity\LanguagesisDefault";b:1;s:43:"LanguagesBundle\Entity\LanguagesupdatedAt";O:8:"DateTime":3:{s:4:"date";s:26:"2018-04-20 22:45:50.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:12:"Europe/Paris";}s:43:"LanguagesBundle\Entity\LanguagesimageFile";N;s:39:"LanguagesBundle\Entity\Languagesimage";s:31:"Flag_of_France_-_Circle-512.png";s:43:"LanguagesBundle\Entity\LanguagesimageSize";i:23352;s:44:"LanguagesBundle\Entity\LanguagesuserClient";N;}}}
Here's my code:
User.php
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use UserBundle\Entity\UserAdmin;
use UserBundle\Entity\UserProfessional;
use UserBundle\Entity\UserClient;
use UserBundle\Entity\Adress;
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"client" = "UserClient", "professional"="UserProfessional", "admin"="UserAdmin"})
*/
abstract class User extends BaseUser {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
protected $firstname;
//[...] getters and setters
Entity: UserClient.php
<?php
// src/UserBundle/Entity/UserGuest.php
namespace UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
*/
class UserClient extends User {
/**
* #ORM\Column(type="array")
* #ORM\OneToMany(targetEntity="LanguagesBundle\Entity\Languages", mappedBy="userClient")
*/
private $languages;
public function __construct() {
parent::__construct();
$this->languages = new ArrayCollection();
}
/**
* Set languages
*
* #param array $languages
*
* #return UserClient
*/
public function setLanguages($languages)
{
$this->languages = $languages;
}
/**
* Get languages
*
* #return array
*/
public function getLanguages()
{
return $this->languages;
}
/**
* Add language
*
* #param \LanguagesBundle\Entity\Languages $language
*
* #return UserClient
*/
public function addLanguage(LanguagesBundle\Entity\Languages $language)
{
$this->languages[] = $language;
return $this;
}
/**
* Remove language
*
* #param \LanguagesBundle\Entity\Languages $language
*/
public function removeLanguage(\LanguagesBundle\Entity\Languages $language)
{
$this->languages->removeElement($language);
}
}
Entity Languages.php
<?php
namespace LanguagesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
*
* #Vich\Uploadable
*/
class Languages
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\Column(type="string", length=2, unique=true)
*/
private $short_slug;
/**
* #var string
*
* #ORM\Column(type="string", length=5, unique=true)
* #Assert\NotBlank()
*/
private $long_slug;
/**
* #var string
*
* #ORM\Column(type="boolean", nullable=true)
*/
private $isDefault;
/**
* #ORM\Column(type="datetime")
* #var \DateTime
*/
private $updatedAt;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Assert\File(
* maxSize="1M",
* mimeTypes={"image/png", "image/jpeg", "image/pjpeg"}
* )
* #Vich\UploadableField(mapping="language_images", fileNameProperty="image", size="imageSize")
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255, nullable = true)
* #var string
*/
private $image;
/**
* #ORM\Column(type="integer")
*
* #var integer
*/
private $imageSize;
/**
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\UserClient", inversedBy="languages")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $userClient;
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*/
public function setImageFile(?File $image = null): void
{
$this->imageFile = $image;
if (null !== $image) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
}
public function getImageFile()
{
return $this->imageFile;
}
/**
* Constructor
*/
public function __construct()
{
$this->updatedAt = new \DateTime();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Languages
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set shortSlug
*
* #param string $shortSlug
*
* #return Languages
*/
public function setShortSlug($shortSlug)
{
$this->short_slug = $shortSlug;
return $this;
}
/**
* Get shortSlug
*
* #return string
*/
public function getShortSlug()
{
return $this->short_slug;
}
/**
* Set longSlug
*
* #param string $longSlug
*
* #return Languages
*/
public function setLongSlug($longSlug)
{
$this->long_slug = $longSlug;
return $this;
}
/**
* Get longSlug
*
* #return string
*/
public function getLongSlug()
{
return $this->long_slug;
}
/**
* Set isDefault
*
* #param boolean $isDefault
*
* #return Languages
*/
public function setIsDefault($isDefault)
{
$this->isDefault = $isDefault;
return $this;
}
/**
* Get isDefault
*
* #return boolean
*/
public function getIsDefault()
{
return $this->isDefault;
}
/**
* Set updatedAt
*
* #param \DateTime $updatedAt
*
* #return Languages
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set image
*
* #param string $image
*
* #return Languages
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return string
*/
public function getImage()
{
return $this->image;
}
/**
* Set imageSize
*
* #param integer $imageSize
*
* #return Languages
*/
public function setImageSize($imageSize)
{
$this->imageSize = $imageSize;
return $this;
}
/**
* Get imageSize
*
* #return integer
*/
public function getImageSize()
{
return $this->imageSize;
}
/**
* Set userClient
*
* #param \UserBundle\Entity\UserClient $userClient
*
* #return Languages
*/
public function setUserClient(\UserBundle\Entity\UserClient $userClient = null)
{
$this->userClient = $userClient;
return $this;
}
/**
* Get userClient
*
* #return \UserBundle\Entity\UserClient
*/
public function getUserClient()
{
return $this->userClient;
}
/**
* {#inheritdoc}
*/
public function __toString()
{
return $this->getName() ?: '-';
}
}
My FormType: RegistrationTypeClient.php
<?php
// src/UserBundle/Form/UserContactFormType.php
namespace UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use LanguagesBundle\Services\LanguagesService;
use LanguagesBundle\Entity\Languages ;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
class RegistrationClientType extends AbstractType {
protected $service;
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder
->add('languages', EntityType::class, [
'class' => 'LanguagesBundle:Languages',
'choice_label' => 'name',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.name', 'ASC');
},
'label' => 'Langues',
'expanded' => true,
'multiple' => true,
])
;
}
public function getParent() {
// on inclut le formulaire de base d'inscription utilisateur
return 'UserBundle\Form\RegistrationType';
}
public function getBlockPrefix() {
return 'app_user_registration_client';
}
// For Symfony 2.x
public function getName() {
return $this->getBlockPrefix();
}
public function configureOptions(OptionsResolver $resolver) {
$resolver
->setDefaults(array(
'data_class' => \UserBundle\Entity\UserClient::class,
))
//->setRequired('service_languages');
;
}
}
And my Controller:
public function registerClientAction(Request $request) {
/** #var $formFactory FactoryInterface */
//$formFactory = $this->get('fos_user.registration_pro.form.factory');
/** #var $userManager UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
/** #var $dispatcher EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
$user = new UserClient();
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
$form = $this->createForm(RegistrationClientType::class, $user);
$form->setData($user);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS, $event);
$userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
$url = $this->generateUrl('fos_user_registration_confirmed');
$response = new RedirectResponse($url);
}
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
}
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_FAILURE, $event);
if (null !== $response = $event->getResponse()) {
return $response;
}
}
return $this->render('UserBundle\Registration\register_client.html.twig', array(
'form' => $form->createView(),
));
}
Here's a picture showing the data saved in the table
I need the languages to be stored in the database as an array for userClient.
Could you help me please.
Thank's for re-edit my question Philip-B-
Thank's for your reading also.
I have resolve my problem by adding a foreach to transform my object to array:
The code is in my controller:
if ($form->isValid()) {
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS, $event);
foreach ($user->getLanguages() as $value) {
$langue[] = $value->getShortSlug();
}
$user->setLanguages($langue);
$userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
$url = $this->generateUrl('fos_user_registration_confirmed');
$response = new RedirectResponse($url);
}
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
}

Get maximum id inside Doctrine entity

I need to get maximum ID of a table inside of symfony 2.7 entity. But instead of having the id I'm getting this issue.
Notice: Undefined property: AppBundle\Entity\BlogPost::$container
This is my BlogPost entity,
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* BlogPost
*
* #ORM\Table()
* #ORM\Entity
*/
class BlogPost {
const SERVER_PATH_TO_IMAGE_FOLDER = '/uploads';
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="body", type="text")
*/
private $body;
/**
* #var string
*
* #ORM\Column(name="filename", type="text")
*/
private $filename;
/**
* Set filename
*
* #param string $filename
* #return BlogPost
*/
public function setFilename($filename) {
$this->filename = $filename;
return $this;
}
public function setUploader(UploadedFile $file) {
$em = $this->container->get('doctrine.orm.entity_manager');
$highest_id = $em->createQueryBuilder()
->select('MAX(b.id)')
->from('AppBundle:BlogPost', 'b')
->getQuery()
->getSingleScalarResult();
var_dump($highest_id);
exit();// exit for check value
$url = 'uploads/events';
$file_name = 'fsdf.' . $file->guessExtension();
$file->move($url, $file_name);
}
/**
* Get filename
*
* #return string
*/
public function getFilename() {
return $this->filename;
}
/**
* #var boolean
*
* #ORM\Column(name="draft", type="boolean")
*/
private $draft;
/**
* Get id
*
* #return integer
*/
public function getId() {
return $this->id;
}
/**
* Set title
*
* #param string $title
* #return BlogPost
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle() {
return $this->title;
}
/**
* Set body
*
* #param string $body
* #return BlogPost
*/
public function setBody($body) {
$this->body = $body;
return $this;
}
/**
* Get body
*
* #return string
*/
public function getBody() {
return $this->body;
}
/**
* Set draft
*
* #param boolean $draft
* #return BlogPost
*/
public function setDraft($draft) {
$this->draft = $draft;
return $this;
}
/**
* Get draft
*
* #return boolean
*/
public function getDraft() {
return $this->draft;
}
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="blogPosts")
*/
private $category;
public function setCategory(Category $category) {
$this->category = $category;
}
public function getCategory() {
return $this->category;
}
/**
* Unmapped property to handle file uploads
*/
public $file;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null) {
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile() {
return $this->file;
}
/**
* Manages the copying of the file to the relevant place on the server
*/
public function upload() {
// the file property can be empty if the field is not required
if (null === $this->getFile()) {
return;
}
// we use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and target filename as params
$this->getFile()->move(
self::SERVER_PATH_TO_IMAGE_FOLDER, $this->getFile()->getClientOriginalName()
);
// set the path property to the filename where you've saved the file
$this->filename = $this->getFile()->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->setFile(null);
}
/**
* Lifecycle callback to upload the file to the server
*/
public function lifecycleFileUpload() {
$this->upload();
}
/**
* Updates the hash value to force the preUpdate and postUpdate events to fire
*/
public function refreshUpdated() {
// $this->setUpdated(new \DateTime());
}
// ... the rest of your class lives under here, including the generated fields
// such as filename and updated
}
This is the part I'm trying to get max id,
public function setUploader(UploadedFile $file) {
$em = $this->container->get('doctrine.orm.entity_manager');
$highest_id = $em->createQueryBuilder()
->select('MAX(b.id)')
->from('AppBundle:BlogPost', 'b')
->getQuery()
->getSingleScalarResult();
var_dump($highest_id);
exit();// exit for check value
$url = 'uploads/events';
$file_name = 'fsdf.' . $file->guessExtension();
$file->move($url, $file_name);
}
With the help of comments and few research I figured out the issue is in 'entity_manager' part. Is there a way to call doctrine queries inside Entity?
If I were you, i would do something like that :
Controller:
$blogPost= new BlogPost () ;
$em = $this->getDoctrine()->getManager();
//..your code
// I assume you want to do that after a form post
$blogPost = $form->getData();
$id = $em->getRepository('AppBundle:BlogPost')->getMaxId();
$blogPost->setUploader($id);
//...
Repository:
public function getMaxId()
{
$qb = $this->createQueryBuilder('u');
$qb->select('u, MAX(id) as idMax');
return $qb->getQuery()->getSingleResult();
}
Entity:
public function setUploader(UploadedFile $file, $id)
{
var_dump($id);
$url = 'uploads/events';
$file_name = 'fsdf.'.$id.$file->guessExtension();
$file->move($url, $file_name);
}
It should work
I would suggest to hold the EntityManager as a class variable inside your Entity and either instantiate it via the constructor or via a setter-method which you call before you use the setUploader function.
This should be the cleanest and best readable solution.
Form another thread I found this and it's working for me.
global $kernel;
if ( 'AppCache' == get_class($kernel) )
{
$kernel = $kernel->getKernel();
}
$em = $kernel->getContainer()->get( 'doctrine.orm.entity_manager');
I can't use it in controller cause I have an admin class instead of controller on this. Since lot of people suggesting this using entity_manager inside entity is not good practice I'm using the code inside admin class instead of entity.
This is the original thread..
How to use entityManager inside Entity?
Try this in your Controller:
$blogPost= new BlogPost () ;
$em = $this->getDoctrine()->getManager();
$blogPost = $form->getData();
$maxId = $em->getRepository('AppBundle:BlogPost')->createQueryBuilder('b')
->where("MAX(b.id) as maxId")
->getQuery()
->getSingleResult();

Symfony2 DoctrineBehaviors, JordiLlonchCrudGeneratorBundle, LexikFormFilterBundle issue

I use DoctrineBehaviors to apply translation of my entity, and JordiLlonchCrudGenerator to generate my crud, and LexikFormFilterBundle to generate my form filters type.
My form type
class PageFilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'filter_text')
->add('content', 'filter_text')
;
$listener = function(FormEvent $event)
{
// Is data empty?
foreach ($event->getData() as $data) {
if(is_array($data)) {
foreach ($data as $subData) {
if(!empty($subData)) return;
}
}
else {
if(!empty($data)) return;
}
}
$event->getForm()->addError(new FormError('Filter empty'));
};
$builder->addEventListener(FormEvents::POST_BIND, $listener);
}
When i try to filters my entities, the error said hat no field called title in Class Entity\Page.
I understand this problem but i have no idea how to resolve this error, because the field title is into the entity PageTranslation, here my function filters :
protected function filter()
{
$request = $this->getRequest();
$session = $request->getSession();
$filterForm = $this->createForm(new PageFilterType());
$em = $this->getDoctrine()->getManager();
$queryBuilder = $em->getRepository('PageBundle:Page')
->createQueryBuilder('e')
->select('e')
->where('e.deletedAt IS NULL')
;
// Reset filter
if ($request->get('filter_action') == 'reset') {
$session->remove('PageControllerFilter');
}
// Filter action
if ($request->get('filter_action') == 'filter') {
// Bind values from the request
$filterForm->bind($request);
if ($filterForm->isValid()) {
// Build the query from the given form object
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($filterForm, $queryBuilder);
// Save filter to session
$filterData = $filterForm->getData();
$session->set('PageControllerFilter', $filterData);
}
} else {
// Get filter from session
if ($session->has('PageControllerFilter')) {
$filterData = $session->get('PageControllerFilter');
$filterForm = $this->createForm(new PageFilterType(), $filterData);
$this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($filterForm, $queryBuilder);
}
}
return array($filterForm, $queryBuilder);
}
I think that i should customize this line but i don't know how
$queryBuilder = $em->getRepository('PageBundle:Page')
->createQueryBuilder('e')
->select('e')
->where('e.deletedAt IS NULL')
;
Any solution for that ?
Also, i have created a trash for each entity, for exemple if one page is deleted the user can find it on trash,
Exemple : http://snapplr.com/snap/xxmk
So i have no problem with the action restore all, but remove all is not functional
This is my action
public function emptyTrashAction(){
$em = $this->getDoctrine()->getEntityManager();
$entities=$em->getRepository('PageBundle:Page')->findByRemoved();
if($entities){
foreach ($entities as $entity) {
$em->remove($entity);
$em->flush();
}
$this->get('session')->getFlashBag()->add('success', 'La corbeille est vide !!');
return $this->redirect($this->generateUrl('pa_trash'));
}else{
$this->get('session')->getFlashBag()->add('error', 'La corbeille est déjà vide !! ');
return $this->redirect($this->generateUrl('pa'));
}
}
What i wanna do, is to delete all entities where the feild DeletedAt is not empty, how can i do this ?
Thanks //
This is my entity Page Class
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* Page
* #ORM\Table(name="page")
* #ORM\Entity(repositoryClass="Core\PageBundle\Entity\PageRepository")
*
*/
class Page
{
use ORMBehaviors\Translatable\Translatable;
use ORMBehaviors\Timestampable\Timestampable;
use ORMBehaviors\SoftDeletable\SoftDeletable;
use ORMBehaviors\Blameable\Blameable;
public function __call($method, $arguments)
{
return \Symfony\Component\PropertyAccess\PropertyAccess::createPropertyAccessor()->getValue($this->translate(), $method);
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="nbview", type="integer", nullable=true)
*/
private $nbview;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set nbview
*
* #param integer $nbview
* #return Page
*/
public function setNbview($nbview)
{
$this->nbview = $nbview;
return $this;
}
/**
* Get nbview
*
* #return integer
*/
public function getNbview()
{
return $this->nbview;
}
public function getUpdateLogMessage(array $changeSets = [])
{
return 'Changed: '.print_r($changeSets, true);
}
public function getRemoveLogMessage()
{
return 'removed!';
}
And this is the translation page class
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* #ORM\Table(name="page_lang")
* #ORM\Entity()
*/
class PageTranslation
{
use ORMBehaviors\Translatable\Translation;
use ORMBehaviors\Sluggable\Sluggable;
/**
* #inheritdoc
*/
public function getSluggableFields()
{
return ['title'];
}
/**
* #inheritdoc
*/
public function getSlug()
{
if (!$this->slug) {
$this->generateSlug();
}
return $this->slug;
}
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string $content
*
* #ORM\Column(name="content", type="text")
*/
private $content;
/**
* #var string $meta
*
* #ORM\Column(name="meta", type="text", nullable=true)
*/
private $meta;
public function getId(){
return $ths->id;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set title
*
* #param string $title
* #return Page
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Set content
*
* #param string $content
* #return Page
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* #param $method
* #param $args
*
* #return mixed
*/
/**
* Set meta
*
* #param string $meta
* #return PageTranslation
*/
public function setMeta($meta)
{
$this->meta = $meta;
return $this;
}
/**
* Get meta
*
* #return string
*/
public function getMeta()
{
return $this->meta;
}
}

Automatic translation before serialization in symfony2 with JMSSerializerBundle

I have some logic to apply after getting entities from database( by findAll() ) and before serializing the result to json.
I want to add translation on some fields. I know that I can do it manually by iterating on each entity and apply my logic in controller. But I need a better way to do it.
Is there a suggestions to make this automatic ?
I've got similar problem and tried to resolve this using custom handler but with no success, so i created compiler pass and override JsonSerializationVisitor where string values are serialized with TranslatableJsonSerializationVisitor class:
namespace tkuska\DemoBundle\Serialization;
use JMS\Serializer\JsonSerializationVisitor;
use JMS\Serializer\Context;
class TranslatableJsonSerializationVisitor extends JsonSerializationVisitor
{
/**
* #var \Symfony\Component\Translation\Translator;
*/
private $translator;
public function visitString($data, array $type, Context $context)
{
if (in_array('translatable', $type['params'])) {
return (string) $this->translator->trans($data);
}
return (string) $data;
}
public function setTranslator(\Symfony\Component\Translation\Translator $translator)
{
$this->translator = $translator;
}
}
and compiler:
namespace tkuska\DemoBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class TranslatableJsonSerializationCompiler implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$jmsJsonSerializationVisitor = $container->getDefinition('jms_serializer.json_serialization_visitor');
$jmsJsonSerializationVisitor->setClass('tkuska\DemoBundle\Serialization\TranslatableJsonSerializationVisitor');
$jmsJsonSerializationVisitor->addMethodCall('setTranslator', array(new Reference('translator')));
}
}
in entity i set annotation for type 'string' with param 'translatable'
/**
* #VirtualProperty
* #Groups({"details"})
* #Type("string<'translatable'>")
* #return order status
*/
public function getOrderStatus(){
return 'status.ordered';
}
Of course 'status.ordered' is my translation key.
Thank you #zizoujab. Very useful post. I made a small improvement to it to call parent method and to unset my parameters, so that i can use this way of altering data on more complex types like array, that already have parameters and used
/**
* #VirtualProperty
* #Groups({"details"})
* #Type("string<translatable>")
* #return order status
*/
instead of
/**
* #VirtualProperty
* #Groups({"details"})
* #Type("string<'translatable'>")
* #return order status
*/
to convert the string 'translatable' into a parameter name instead of parameter value thus you can pass more complex parameters like this, and allow me to unset this parameters before calling the parent method.
/**
* #VirtualProperty
* #Groups({"details"})
* #Type("string<translatable<set of parameters>>")
* #return order status
*/
Code:
<?php
namespace Mktp\DefaultBundle\Service\JmsSerializer;
use JMS\Serializer\Context;
use JMS\Serializer\JsonSerializationVisitor;
use Symfony\Component\Translation\Translator;
class TranslatableJsonSerializationVisitor extends JsonSerializationVisitor
{
/**
* #var Translator;
*/
private $translator;
/**
* #param string $data
* #param array $type
* #param Context $context
* #return string
*/
public function visitString($data, array $type, Context $context)
{
$translatable = $this->getParameters('translatable', $type['params']);
if (count($translatable)) {
$data = (string)$this->translator->trans($data);
}
return parent::visitString($data, $type, $context);
}
/**
* #param array $data
* #param array $type
* #param Context $context
* #return array|\ArrayObject|mixed
*/
public function visitArray($data, array $type, Context $context)
{
$translatable = $this->getParameters('translatable', $type['params']);
if (count($translatable)) {
foreach ($data as $key => $value) {
if (is_string($value)) {
$data[$key] = (string)$this->translator->trans($value);
}
}
}
return parent::visitArray($data, $type, $context);
}
/**
* #param Translator $translator
*/
public function setTranslator(Translator $translator)
{
$this->translator = $translator;
}
/**
* #param string $type
* #param array $parameters
* #param bool $unsetParameters
* #return array
*/
protected function getParameters($type, &$parameters, $unsetParameters = true)
{
$result = array();
foreach ($parameters as $key => $parameter) {
if ($parameter['name'] == $type) {
$result[] = $parameter;
if ($unsetParameters) {
unset($parameters[$key]);
}
}
}
$parameters = array_values($parameters);
return $result;
}
}

Resources