I'm building a web application with Symfony 3 and I need a REST API for mobiles applications. I use FosUserBundle and FosRestBundle.
Here's a simple controller:
/**
* #Route("/titles/add", name="_addTitle");
* #param Request $request
* #return Response
*/
public function addAction(Request $request)
{
$title = new Title();
$title->setUserId($this->getUser()->getId());
$form = $this->createForm(NewTitle::class, $title);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->get('title.manager')->saveTitle($title);
return $this->redirect('/contacts/add');
}
return $this->render('title/add.html.twig', array(
'form' => $form->createView()
));
}
/**
* #Post("/api/titles")
*/
public function postAction(Request $request)
{
$title = new Title();
$title->setUserId(1);
$form = $this->createForm(NewTitle::class, $title);
$form->submit($request->request->all());
if ($form->isValid()) {
$this->get('title.manager')->saveTitle($title);
return View::create($title, Codes::HTTP_CREATED);
}
return View::create($form, Codes::HTTP_BAD_REQUEST);
}
I have one action for the web application and one action for REST API. Business logic is in a "manager" service.
NewTitle Form Type :
class NewTitle extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label', TextType::class)
->add('save', SubmitType::class, array('label' => 'Add title'));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'csrf_protection' => false
));
}
}
My goal is to keep the code clean and avoid duplication. I have some questions :
Do you see a better way ?
in the postAction, when I have form errors it displays the "submit" field in my JSON response. How can I use same form class but "submit" element only for HTML form ?
Related
There is an error during execute the createForm method.
InvalidArgumentException: Could not load type "ArticleType"
My symfony version is 3.3.*.
I tried to execute the createForm method with Article::class instead of ArticleType::class.
Here is my code, where is the problem?
ArticleController.php
public function createAction(Request $request)
{
$article = new Article();
$form = $this->createForm(ArticleType::class, $article);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// ...
}
return $this->render('admin/article/create.html.twig', [
'form' => $form->createView()
]);
}
ArticleType.php
class ArticleType extends AbstractType
{
private $categoryService;
private $tagService;
public function __construct(CategoryService $categoryService, TagService $tagService)
{
$this->categoryService = $categoryService;
$this->tagService = $tagService;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/.
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
}
public function setDefaultOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'CMS\Bundle\ContentBundle\Entity\Article'
]);
}
}
Resources/config/services.yml (included in app/config/services.yml)
services:
CMS\Bundle\ContentBundle\Form\ArticleType:
arguments: ['#cms.core.service.category', '#cms.core.service.tag']
tags: [form.type]
.
It looks like your custom form class can't be found in the current namespace(s). Try adding use CMS\Bundle\ContentBundle\Form\ArticleType; (or something similar) to your controller.
I am having problem with creating new Collection entity with Form.
I want to create new Collection entity with form and then to be redirected to collections page with 'collection_user_collections' route, and be able to see new collection in user's collections list. But instead when I press submit button on form, I get following error:
No route found for "POST /profile/": Method Not Allowed (Allow: GET, HEAD)
Below is my code:
class Collection{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
private $name;
private $url;
private $type;
const STATUS_PRIVATE = 0;
const STATUS_PUBLIC = 1;
/**
* #ORM\ManyToOne(targetEntity="MyMini\UserBundle\Entity\User", inversedBy="collections")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
private $date_created;
private $date_modified;
/* getters and setters are here*/
}
I am using CollectionType to build form:
class CollectionType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text')
->add('type', 'choice', array('choices' => array(
Collection::STATUS_PRIVATE => 'Private',
Collection::STATUS_PUBLIC => 'Public',
)))
->add('save', 'submit', array('label' => 'Create Collection'))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyMini\CollectionBundle\Entity\Collection'
));
}
public function getName()
{
return 'mymini_collectionbundle_collection';
}
}
This is createAction, here I tried to insert current user's username and date when entity was created. I am using FOSUserBundle to manage app users:
/**
* #Route("/create-collection/", name="collection_create_collection")
* #Template()
*/
public function createAction(Request $request)
{
$collection = new Collection();
$user = $this->get('security.token_storage')->getToken()->getUser();
$username = $user->getUsername();
$form = $this->createForm(new CollectionType(), $collection);
$form->handleRequest($request);
if ($form->isValid() && $form->isSubmitted()) {
$em = $this->getDoctrine()->getManager();
$collection->setUser($user);
$collection->setDateCreated(new \DateTime());
$em->persist($collection);
$em->flush();
return $this->redirectToRoute('collection_user_collections', array('username' => $username));
}
return array('collection'=>$collection, 'form' => $form->createView());
}
Twig for form:
<div class="collection-create">
<h3 id="create-collection">Create a collection</h3>
<a class="close-reveal-modal" aria-label="Close">×</a>
{{ form(form) }}
</div>
The exception you're receiving is expected. You are calling the createForm method without passing all necessary arguments. The right way to create a form is:
$this->createForm(
new CollectionType(),
$collection,
array(
'action' => $this->generateUrl('collection_create_collection') ),
'method' => 'PUT', // or 'POST'
)
);
In my code I need to download from database some data and put it to the form, but I don't know how to get doctrone outside Controller class.
I tried create new service, but it didn't work (I think I can't use in this case __controller(), am I right?). I tried also transfer instance of the controller to the parameters of buildForm() method but I got message: FatalErrorException: Compile Error: Declaration of MyBundle\Form\Type\TemplateType::buildForm() must be compatible with that of Symfony\Component\Form\FormTypeInterface::buildForm() ).
This is my code:
class TemplateType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', 'text')
// ...
->add('description', 'textarea');
}
public function getName() {
return 'template';
}
}
How can I use inside buildForm() doctrine?
In order to send data from doctrine to your form, you need to do this into your controller:
public function doSomethingWithOneObjectAction( $id )
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository( 'AcmeBundle:ObjectEntity' )->find( $id );
if ( ! $entity) {
throw $this->createNotFoundException( 'Unable to find Object entity.' );
}
$form = $this->createForm(
new TemplateType(),
$entity
);
return array(
'entity' => $entity,
'form' => $form->createView()
);
}
If you want to access a service from container inside your form type, you need first to register it as an service and inject into it the services you need. Something like this
I'm developing a RESTFul API in Symfony 2.3.* with FOSUserBundle and FOSRestBundle, and I'm having trouble understanding how to create a registration method.
My controller look like this :
class UserRestController extends FOSRestController
{
//Other Methods ...
public function postUserAction()
{
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->createUser();
$param = $paramFetcher->all();
$form = $this->createForm(new UserType(), $user);
$form->bind($param);
if ($form->isValid() == false)
return $this->view($form, 400);
$userManager->updateUser($user);
return $this->view('User Created', 201);
}
//...
}
And my UserType class :
class UserType extends BaseType
{
public function __construct($class = "User")
{
parent::__construct($class);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username', 'username')
->add('email', 'email')
->add('plainPassword', 'repeated', array(
'first_name' => 'password',
'second_name' => 'confirm',
'type' => 'password'
))
->add('lastname')
->add('firstname')
->add('job_position')
->add('phone')
->add('company_name')
->add('website')
->add('sector')
->add('address')
->add('city')
->add('zip_code')
->add('country')
->add('billing_infos_same_as_company')
->add('billing_address')
->add('billing_city')
->add('billing_zip')
->add('billing_country')
->add('putf')
->add('das');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Wipsea\UserBundle\Entity\User',
'csrf_protection' => false
));
}
public function getName()
{
return 'wipsea_user_registration';
}
}
When I test it, no matter what the form isn't valid and shows no error.
And when I try to get the request :
"Validation Failed" "This form should not contain extra fields."
Is there a way to properly validate the form ?
EDIT : Updating my problem.
I would recommend you this tutorial in 3 parts - there is everything you need:
http://welcometothebundle.com/symfony2-rest-api-the-best-2013-way/
http://welcometothebundle.com/web-api-rest-with-symfony2-the-best-way-the-post-method/
http://welcometothebundle.com/symfony2-rest-api-the-best-way-part-3/
If you want to provide complex user validation you should create UserType form and pass data to this form instead of directly setting all properties:
public function postAction()
{
$user = new User();
$form = $this->createForm(new UserType(), $user);
$form->handleRequest($this->getRequest());
if ($form->isValid()) {
// propel version
$user->save();
$response = new Response();
$response->setStatusCode(201);
// set the `Location` header only when creating new resources
$response->headers->set('Location',
$this->generateUrl(
'acme_demo_user_get', array('id' => $user->getId()),
true // absolute
)
);
return $response;
}
// return form validation errors
return View::create($form, 400);
}
In part 2 of this tutorial you have all information about creating form, passing data and validating it with RestBundle.
There is also a lot information about best practices using REST with Symfony2.
Take a look at this code:
https://github.com/piotrjura/fitness-api/blob/master/src/Fitness/FitnessBundle/Service/UsersService.php#L40
https://github.com/piotrjura/fitness-api/blob/master/src/Fitness/FitnessBundle/Controller/UsersController.php#L30
Also check validation.yml and entity serializer yml files.
You don't need forms to do the validation. And you definitly should not put the user creation and validation logic inside a controller. In case you'd like to make use of that form anyway later, eg. render it on the backend side, you'll have to write the same code twice.
I had to have the getName() method return '' in order for it to work for me.
https://github.com/FriendsOfSymfony/FOSRestBundle/issues/585
I have entity Message with ManyToOne relation with entity User:
class Message
{
...
/**
* #var User $sender
*
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User")
* #ORM\JoinColumn(name="sender_id", referencedColumnName="id")
*
**/
private $sender;
...
}
If $sender doesn't have email value i need to create new field for my form, so i create form for Message entity in Contoller:
$user = $this->getUser();
$message = new Message();
$message->setSender($user);
$formBuilder = $this->createFormBuilder($message, array(
'cascade_validation' => true
));
$formBuilder->add('body', 'textarea');
if (!$user->getEmail()) {
$formBuilder->add('email', 'email', array(
'property_path' => 'sender.email'
));
}
And i have some validation rules in validation.yml for entity User. Can i automatically validate this field by my validation rules for User entity in another entity's form? I don't know how to do it.
I found workaround right now: create new MissingEmailType:
class MissingEmailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Entity\User',
'validation_groups' => array(
'MissingEmail'
),
));
}
public function getName()
{
return 'missing_email';
}
}
But it looks complicated. Is there any better solutions?
You could redirect the page to the user profile page instead of loading the message form and state that the user needs to add an email prior to adding the message. If you redirect quickly or create a popup, the user might not be turned off as long as they can return to the original page after adding their email. Then validtion is simple since you only need to validate the user entity.