Symfony2 - Adding Swiftmailer as a service - email

I'd like to move my email code from my controller into a service.
I've done the following thus far:
created the entry in services.yml
created a EmailManager.php file inside acme/demobundle/services/EmailManager.php
Could use some help on what needs to go into the EmailManager.php and how to call it in the controller?
services.yml
services:
email_manager:
class: Acme\DemoBundle\Services\EmailManager
arguments: [#request_stack, #mailer]
scope: request
EmailManager.php
<?php
// src/Acme/DemoBundle/Services/EmailManager.php
namespace Acme\DemoBundle\Services;
class EmailManager
{
private $mailer;
private $request;
public function __construct(RequestStack $requestStack, $mailer)
{
$this->request = $requestStack->getCurrentRequest();
$this->mailer = $mailer;
}
What needs to go here? Do I just copy/paste the code from the contactAction below into here?
}
Controller code with contactAction that I would like to move out of the controller into EmailManager service:
/**
* #Route("/", name="contact")
* #Template("AcmeDemoBundle:Default:index.html.twig")
*/
public function contactAction(Request $request)
{
$form = $this->createForm(new ContactType());
if ($request->isMethod('POST')) {
$form->submit($request);
if ($form->isValid()) {
$message = \Swift_Message::newInstance()
->setSubject($form->get('subject')->getData())
->setFrom($form->get('email')->getData())
->setTo('example#gmail.com')
->setBody(
$this->renderView(
'AcmeDemoBundle:Default:index.html.twig',
array(
'ip' => $request->getClientIp(),
'name' => $form->get('name')->getData(),
'message' => $form->get('message')->getData()
)
)
);
$this->get('mailer')->send($message);
$request->getSession()->getFlashBag()->add('success', 'Your email has been sent! Thanks!');
return $this->redirect($this->generateUrl('contact'));
}
}
return array(
'form' => $form->createView()
);
}
ContactType Form
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'text', array(
'attr' => array(
'placeholder' => 'What\'s your name?',
'pattern' => '.{2,}' //minlength
)
))
->add('email', 'email', array(
'attr' => array(
'placeholder' => 'So I can get back to you.'
)
))
->add('subject', 'text', array(
'attr' => array(
'placeholder' => 'The subject of your message.',
'pattern' => '.{3,}' //minlength
)
))
->add('message', 'textarea', array(
'attr' => array(
'cols' => 90,
'rows' => 10,
'placeholder' => 'And your message to me...'
)
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$collectionConstraint = new Collection(array(
'name' => array(
new NotBlank(array('message' => 'Name should not be blank.')),
new Length(array('min' => 2))
),
'email' => array(
new NotBlank(array('message' => 'Email should not be blank.')),
new Email(array('message' => 'Invalid email address.'))
),
'subject' => array(
new NotBlank(array('message' => 'Subject should not be blank.')),
new Length(array('min' => 3))
),
'message' => array(
new NotBlank(array('message' => 'Message should not be blank.')),
new Length(array('min' => 5))
)
));
$resolver->setDefaults(array(
'constraints' => $collectionConstraint
));
}
public function getName()
{
return 'contact';
}
}

You can customize this as you see fit, but that's a general idea and a very quick draft to guide you:
public function send($subject, $recipientName, $recipientEmail, $bodyHtml, $bodyText)
{
/* #var $mailer \Swift_Mailer */
if(!$this->mailer->getTransport()->isStarted()){
$this->mailer->getTransport()->start();
}
/* #var $message \Swift_Message */
$message = $this->mailer->createMessage();
$message->setSubject($subject);
$message->setBody($bodyHtml, 'text/html');
$message->addPart($bodyText, 'text/plain', 'UTF8');
$message->addTo($recipientEmail, $recipientName);
$message->setFrom( array('example#gmail.com' => 'Chance') );
$this->mailer->send($message);
$this->mailer->getTransport()->stop();
}
Room for Improvement
You could have:
An email data model that would contain the fields necessary for an email (like $subject, $recipientEmail, ...)
A composer that would compose your email from your request
A sender that would send your email
EMAIL MODEL would look something like this:
/**
* Email Data Model
*/
class Email implements EmailInterface
{
/**
* The text part of the message.
*
* #var string
*/
protected $bodyText;
// etc...etc..
}
You'd have an EmailInterface too:
/**
* Email interface
*/
interface EmailInterface
{
/**
* #return string
*/
public function getBodyText();
// etc...etc..
}
THE SENDER would look like this (if kept inside EmailManager):
public function send(EmailInterface $email)
{
//...
}
THE COMPOSER would look like this (if kept inside EmailManager):
public function composeEmail(Request $request)
{
//...
return $email;
}
Note: Composer and Sender could also be a separate service for better reuse, that's up to you I guess. Here is what they would look like if there were just functions in your EmailManager

Related

Zend - Dynamic form dropdown

I start with zend framework and I'm having trouble implementing a dynamic drop-down list.
I need to create a simple dropdown list of events select from the database.
This is my Module class :
public function getFormElementConfig()
{
return array(
"factories" => [
'participant_form' => function (ServiceManager $serviceManager) {
/** #var EntityManager $entityManager */
$entityManager = $serviceManager->get("doctrine.entitymanager.orm_default");
$events = $entityManager->getRepository('Application\Entity\Event')->findAll();
$eventForSelect = array();
foreach ($events as $event) {
$eventForSelect[$event->getId()] = $event->getName();
}
/** #var \Zend\Form\Form $form */
$form = new ParticipantForm();
$form->setHydrator(new DoctrineHydrator($entityManager));
$form->setObject(new Participant());
$form->setOption('event_for_select', $eventForSelect);
return $form;
},
]
);
}
but I do not know how to get the option 'event_for_select' in my form :
class ParticipantForm extends Form
{
public function __construct($name = null)
{
parent::__construct('user');
$this->setAttribute('class', 'form-horizontal');
$this->add([
'name' => 'id',
'type' => 'Hidden',
]);
$this->add([
'name' => 'firstname',
'type' => 'Text',
'options' => [
'label' => 'Prénom',
],
]);
$this->add([
'name' => 'event',
'type' => 'Select',
'options' => [
'label' => 'Event',
'value_options' => // ?? $event_for_select
],
]);
Thanks for your help !
Add field in your form class:
class ParticipantForm extends Form
{
protected $eventForSelect;
public function setEventForSelect($eventForSelect)
{
$this->eventForSelect = $eventForSelect;
// update field value options
$this->get('event')
->setValueOptions($this->eventForSelect);
return $this;
}
// rest of form class code
}
Then use setEventForSelect() method in your factory:
/** #var \Zend\Form\Form $form */
$form = new ParticipantForm();
$form->setHydrator(new DoctrineHydrator($entityManager));
$form->setObject(new Participant());
$form->setEventForSelect($eventForSelect);
return $form;
And then in form:
$this->add([
'name' => 'event',
'type' => 'Select',
'options' => [
'label' => 'Event',
'value_options' => $this->eventForSelect,
],
]);

Zf2 - Doctrine Hydrator Fieldset

I'm new with ZendFramework and Doctrine 2.
I try to implement a form with fieldset and Doctrine Hydrator.
The form and validators work good, but not Hydrator.
My source code:
AuthController.php
public function signupAction()
{
$userForm = new UserForm($this->getServiceLocator()->get('Doctrine\ORM\EntityManager'));
$request = $this->getRequest();
if ($request->isPost()) {
var_dump($request->getPost());
$userForm->setData($request->getPost());
if ($userForm->isValid()) {
$user = $userForm->getData();
var_dump($user);
}
}
return new ViewModel(array(
'form' => $userForm,
));
}
UserForm.php
class UserForm extends Form
{
public function __construct($em) {
parent::__construct('User');
$this->setAttribute('method', 'post')
->setHydrator(new DoctrineObject($em, 'User\Entity\User'));
$this->add(array(
'type' => 'User\Form\Fieldset\UserFieldset',
'options' => array(
'use_as_base_fieldset' => true
)
));
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Send'
)
));
}
}
UserFieldset.php
class UserFieldset extends Fieldset implements InputFilterProviderInterface, ObjectManagerAwareInterface
{
/**
* #var ObjectManager
*/
private $objectManager;
public function __construct()
{
parent::__construct('User');
// Id
$this->add(array(
'name' => 'id',
'type' => 'Zend\Form\Element\Hidden',
));
// FirstName
$this->add(array(
'name' => 'firstName',
'attributes' => array(
'required' => 'required',
),
'options' => array(
'label' => 'Prénom',
),
'type' => 'Zend\Form\Element\Text',
));
}
public function getInputFilterSpecification()
{
return array(
'firstName' => array(
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 2,
'max' => 100,
'messages' => array(
\Zend\Validator\StringLength::TOO_LONG => 'Le prénom est trop long (maximum %max% caractères)',
\Zend\Validator\StringLength::TOO_SHORT => 'Le prénom est trop court (minimum %min% caractères)',
),
),
),
),
),
'id' => array(
'required' => false,
),
);
}
public function setOption($key, $value) { }
/**
* Set the object manager
*
* #param ObjectManager $objectManager
*/
public function setObjectManager(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* Get the object manager
*
* #return ObjectManager
*/
public function getObjectManager()
{
return $this->getObjectManager();
}
}
I have not a User object, but a array...
A idea?
Thanks you!
Aurélien

How to validate my form in Zend Framework 2 and Doctrine?

so i have some entities and i want to validate my forms, i use Zend Framework 2 and Doctrine Orm, Update Thanks to Notuser : now i get this error :
Fatal error: Call to undefined method Zend\ServiceManager\ServiceManager::initForm() in C:\wamp2\www\test\module\Application\src\Application\Controller\BlogController.php
this is my model.config.php :
'doctrine' => array(
'driver' => array(
'application_entities' => array(
'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/Application/Entity')
),
'application_forms' => array(
'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/Application/Form')
),
'application_inputs_filters' => array(
'class' =>'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
'cache' => 'array',
'paths' => array(__DIR__ . '/../src/Application/InputFilter')
),
'orm_default' => array(
'drivers' => array(
'Application\Entity' => 'application_entities',
'Application\Form' => 'application_forms',
'Application\InputFilter' => 'application_inputs_filters'
)
)
)
),
and this my controller :
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Entity\Article;
use Application\Entity\Image;
use Application\Form\ArticleForm;
class BlogController extends AbstractActionController
{
protected $_objectManager;
public function addAction()
{
$form = $this->getServiceLocator('Application\Form\ArticleForm');
$form->initForm();
$request = $this->getRequest();
$form->setData($request->getPost());
$article = new Article();
if ($this->zfcUserAuthentication()->hasIdentity()) {
if ($form->isValid())
{
$file = $this->params()->fromFiles('url');
$adapter = new \Zend\File\Transfer\Adapter\Http();
$adapter->setDestination('public/upload');
if($adapter->receive($file['name'])){
$article->setTitle($this->getRequest()->getPost('title'));
$article->setDate(new \DateTime($this->getRequest()->getPost('date')));
$article->setContent($this->getRequest()->getPost('content'));
$article->setPublication($this->getRequest()->getPost('publication'));
$image = new Image();
$image->setUrl($file['name']);
$image->setAlt($this->getRequest()->getPost('alt'));
$article->setImage($image);
$this->getObjectManager()->persist($article);
$this->getObjectManager()->flush();
$newId = $article->getId();
return $this->redirect()->toRoute('blog');
}
}
}
else
{
return $this->redirect()->toRoute('user');
}
return new ViewModel(array('article' => $article));
}
And this is my ArticleForm :
class ArticleForm extends Form {
public function __construct()
{
parent::__construct('UserEntry');
$this->setAttribute('method', 'post');
$this->setAttribute('enctype', 'multipart/form-data');
$this->setAttribute('class', 'contact_form');
}
/**
*
*/
public function initForm()
{
$this->addFormFields(); //function where we added all fields
$articleInputFilter = new ArticleInputFilter();
$this->setInputFilter($articleInputFilter->getInputFilter()); //Asign input Filter to form
}
/**
*
*/
protected function addFormFields()
{
$this->addSubmit();
$this->addTitle();
$this->addContent();
$this->addDate();
$this->addPublication();
$this->addImage();
}
/**
*
*/
protected function addTitle()
{
$this->add(array(
'name' => 'title',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => _('Title')
),
));
}
/**
*
*/
protected function addContent()
{
$this->add(array(
'name' => 'content',
'attributes' => array(
'type' => 'text',
),
'options' => array(
'label' => _('Content')
),
));
}
/**
*
*/
protected function addDate()
{
$this->add(array(
'name' => 'date',
'attributes' => array(
'type' => 'date',
),
'options' => array(
'label' => _('Date'),
'id' => 'datepicker',
),
));
}
/**
*
*/
protected function addPublication()
{
$this->add(array(
'name' => 'publication',
'attributes' => array(
'type' => 'checkbox',
),
'options' => array(
'label' => _('Publication'),
'use_hidden_element' => true,
'checked_value' => 1,
'unchecked_value' => 'no',
),
));
}
/**
*
*/
protected function addImage()
{
$this->add(array(
'name' => 'Image',
'attributes' => array(
'type' => new ImageForm(),
),
'options' => array(
'label' => _('Image')
),
));
}
/**
*
*/
protected function addSubmit()
{
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => _('Add'),
'class' => 'submit',
),
));
}
}
Finally this is my ArticleInputFilter :
class ArticleInputFilter extends InputFilter implements InputFilterAwareInterface
{
/**
* #var string
*/
public $title;
/**
* #var int
*/
public $image;
/**
* #var string
*/
public $content;
/**
* #var Date
*/
public $date;
/**
* #var Boolean
*/
public $publication;
/**
* #param $data
*/
public function exchangeArray($data)
{
$this->title = (isset($data['title'])) ? $data['title'] : $this->title;
$this->image = (isset($data['image'])) ? $data['image'] : $this->image;
$this->content = (isset($data['content'])) ? $data['content'] : $this->content;
$this->publication = (isset($data['publication'])) ? $data['publication'] : $this->publication;
$this->date = (isset($data['date'])) ? $data['date'] : $this->date;
}
/**
* #param InputFilterInterface $inputFilter
* #return void|InputFilterAwareInterface
* #throws \Exception
*/
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
/**
* #return InputFilter|InputFilterInterface
*/
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 6,
'max' => 100,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'content',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 10,
),
),
),
)));
$inputFilter->add($factory->createInput(array(
'name' => 'publication',
'required' => false,
)));
$inputFilter->add($factory->createInput(array(
'name' => 'date',
'required' => true,
)));
$inputFilter->add($factory->createInput(array(
'name' => 'image',
'required' => true,
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
That it i write my hole code except my ImageForm and ImageInputFilter, So please if someone has any idea how to do that i will be very appreciative ;)
In controller
$form = $this->getServiceLocator('Application\Form\BlogForm");//don't forger to add Application\Form\BlogForm to module.config.php and check your namespace
$form->initForm();//explained further
/** #var \Zend\Http\Request $request */
$request = $this->getRequest();//whole request fo #var for further using
if ($request->isPost()) { //there is something in post
$form->setData($request->getPost()); //data from post is added for further validation
if ($form->isValid()) { //Form is Valid lets save form
$files = $request->getFiles()->toArray(); //array witch files use var_dump($files);die; to show structure
$article = new Article();
$article->setTitle($request->getPost('title'));
...
if(!empty($files)&&$files['image']['error']==0) {
$article->setImage($functionToCreateImageEntityFromFile(($files['image']));
}
...
$this->getObjectManager()->persist($article);
$this->getObjectManager()->flush();
$newId = $article->getId();
return $this->redirect()->toRoute('blog');
}
//form is not valid so you can display form with Errors
} //there is no post so you can display clean form
Next let's take form:
(it's my proposal)
namespace Application\Form; //check your namespace
use Zend\Form\Form;
use Application\InputFilter\BlogInputFilter; //check your namespace
class BlogForm extends Form {
public function __construct()
{
parent::__construct('UserEntry');
$this->setAttribute('method', 'post');
$this->setAttribute('enctype', 'multipart/form-data');
}
/**
*
*/
public function initForm()
{
$this->addFormFields(); //function where we added all fields
$blogInputFilter = new BlogInputFilter(); //Input Filter for Validation
$this->setInputFilter($BlogInputFilter->getInputFilter()); //Asign input Filter to form
}
/**
*
*/
protected function addFormFields()
{
$this->addSubmit();
$this->addTitle();
$this->addImage();
...//add more here
}
/**
*
*/
protected function addTitle()
{
$this->add(array(
'name' => 'title',
'attributes' => array(
'type' => 'text',
'id' => 'title',
'class' => 'text'
),
'options' => array(
'label' => _('Title') //it's only for multilang you can put here any string
),
));
}
/**
*
*/
protected function addImage()
{
$this->add(array(
'name' => 'image',
'attributes' => array(
'type' => 'file',
'id' => 'image'
),
'options' => array(
'label' => _('Image'),
),
));
}
/**
*
*/
protected function addSubmit()
{
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => _('Save'),
'class' => 're',
),
));
}
}
Now its Time for InputFilter
namespace Application\InputFilter;//check your namespace
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class BlogInputFilter implements InputFilterAwareInterface
{
/**
* #var int
*/
public $title;
/**
* #var string
*/
public $image;
//add all fields
/**
* #param $data
*/
public function exchangeArray($data)
{
$this->title = (isset($data['title'])) ? $data['title'] : $this->title;
$this->image = (isset($data['image'])) ? $data['image'] : $this->image;
//add fields
}
/**
* #param InputFilterInterface $inputFilter
* #return void|InputFilterAwareInterface
* #throws \Exception
*/
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
/**
* #return InputFilter|InputFilterInterface
*/
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$factory = new InputFactory();
$inputFilter->add($factory->createInput(array(
'name' => 'title',
'required' => true,//required field
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,//limit from 1 to 100 chars
),
),
),
)));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
to read:
Forms: http://framework.zend.com/manual/2.0/en/modules/zend.form.quick-start.html#factory-backed-form-extension
ImputFilter: http://framework.zend.com/manual/2.0/en/modules/zend.input-filter.intro.html
File upload: Can't find good tutorial
module.config.php should have key like this:
'service_manager' => array(
'invokables' => array(
'Application\Form\ArticleForm' => 'Application\Form\ArticleForm',
),
),
There is no need to add Application\Form\ArticleForm to use becouse you use ServiceLocator
This is your problem:
$form = $this->getServiceLocator('Application\Form\ArticleForm');
$form->initForm();
It should be:
$form = $this->getServiceLocator()->get('Application\Form\ArticleForm');
$form->initForm();
The way you have written it, the bracket contents are ignored and it becomes:
$form = $this->getServiceLocator()
which is why you get an "undefined method: ServiceManager::initForm()" error when you call initForm().

$form->isValid says it is invalid, but my form does not show any error message - Zend Framework 2

Im having a problem with the addAction in my CRUD application. On the controller the logic does not pass the $form->isValid() verification but the form does not show any error message.
I tried with this (Thanks Sam):
foreach($form->get('product')->getElements() as $el)
{
echo $el->getName()." = ".$el->getValue()." > ".$el->getMessages()." <br/>";
}
That only show the name and value of the field, but not the error message.
I've tried letting the form totally blank and it fire "Value is required and can't be empty" error messages, but then, i fill each field one by one until i don't get more error messages but the form still invalid.
My form has a Product Fieldset as a base fieldset and a submit button. Inside Product Fieldset i have an ID field, a name field, a price field and a Brand Fieldset. Inside my Brand Fieldset i have a id field. Like this:
ProductForm:
class ProductForm extends Form
{
public function init()
{
// we want to ignore the name passed
parent::__construct('product');
$this->setName('product');
$this->setAttribute('method', 'post');
$this->add(array(
'name' => 'product',
'type' => 'Administrador\Form\ProductFieldset',
'options' => array(
'use_as_base_fieldset' => true
),
));
$this->add(array(
'name' => 'submit',
'type' => 'Submit',
'attributes' => array(
'value' => 'Add',
'id' => 'submitbutton',
),
));
}
}
ProductFieldset:
class ProductFieldset extends Fieldset implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
function __construct($name = null)
{
parent::__construct('product_fieldset');
$this->setHydrator(new ArraySerializableHydrator());
$this->setObject(new Product());
}
public function init()
{
$this->add(array(
'name' => 'id',
'type' => 'Hidden',
));
$this->add(array(
'name' => 'name',
'type' => 'Text',
'options' => array(
'label' => 'Name',
),
));
$this->add(array(
'name' => 'price',
'type' => 'Text',
'options' => array(
'label' => 'Price',
),
));
$this->add(array(
'name' => 'brand',
'type' => 'BrandFieldset',
));
}
public function setServiceLocator(ServiceLocatorInterface $sl)
{
$this->serviceLocator = $sl;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
}
BrandFieldset:
class BrandFieldset extends Fieldset
{
function __construct(BrandTable $brandTable)
{
parent::__construct('brand_fieldset');
//$this->setHydrator(new ClassMethodsHydrator(false))->setObject(new Brand());
$this->setHydrator(new ArraySerializableHydrator());
$this->setObject(new Brand());
$brandSelectOptionsArray = $brandTable->populateSelectBrand();
$this->add(array(
'name' => 'id',
'type' => 'Select',
'options' => array(
'label' => 'Brand',
'empty_option' => 'Please select a brand',
'value_options' => $brandSelectOptionsArray,
),
));
}
}
This is my new Form statement in the addAction:
$formManager = $this->serviceLocator->get('FormElementManager');
$form = $formManager->get('Administrador\Form\ProductForm');
Inside my model 'Product' i have the inputFilters, required filter for 'id' field, required filter for 'name' field. And for the Brand field i created other inputFilter and added it to the main inputFilter:
$brandFilter->add($factory->createInput(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
)));
$inputFilter->add($brandFilter, 'brand');
The weird behavior is that my editAction works fine and has the same logic.
Is it there any form of echoing an internal error message from the form, something that helps me to understand WHY the form is not valid.
EDIT 2013-06-01
Here is my full Controller:
class ProductController extends AbstractActionController
{
protected $productTable;
protected $brandTable;
public function indexAction()
{
return new ViewModel(array(
'products' => $this->getProductTable()->fetchAll(),
));
}
public function addAction()
{
$formManager = $this->serviceLocator->get('FormElementManager');
$form = $formManager->get('Administrador\Form\ProductForm');
$form->get('submit')->setValue('Add');
$request = $this->getRequest();
if ($request->isPost()) {
$product = new Product();
$product->brand = new Brand();
$form->setInputFilter($product->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$product->exchangeArray($form->getData());
$this->getProductTable()->saveProduct($product);
// Redirect to list of products
return $this->redirect()->toRoute('product');
}
}
return new ViewModel(array(
'form' => $form,
));
}
public function editAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('product', array(
'action' => 'add'
));
}
// Get the Product with the specified id. An exception is thrown
// if it cannot be found, in which case go to the index page.
try {
$product = $this->getProductTable()->getProduct($id);
}
catch (\Exception $ex) {
return $this->redirect()->toRoute('product', array(
'action' => 'index'
));
}
$formManager = $this->serviceLocator->get('FormElementManager');
$form = $formManager->get('Administrador\Form\ProductForm');
$brand = $this->getBrandTable()->getBrand($product->brand);
$product->brand = $brand;
$form->bind($product);
$form->get('submit')->setAttribute('value', 'Edit');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($product->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getProductTable()->saveProduct($form->getData());
// Redirect to list of products
return $this->redirect()->toRoute('product');
}
}
return array(
'id' => $id,
'form' => $form,
);
}
public function deleteAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('product');
}
$request = $this->getRequest();
if ($request->isPost()) {
$del = $request->getPost('del', 'No');
if ($del == 'Yes') {
$id = (int) $request->getPost('id');
$this->getProductTable()->deleteProduct($id);
}
// Redirect to list of products
return $this->redirect()->toRoute('product');
}
return array(
'id' => $id,
'product' => $this->getProductTable()->getProduct($id)
);
}
public function getProductTable()
{
if (!$this->productTable) {
$sm = $this->getServiceLocator();
$this->productTable = $sm->get('Administrador\Model\ProductTable');
}
return $this->productTable;
}
public function getBrandTable()
{
if (!$this->brandTable) {
$sm = $this->getServiceLocator();
$this->brandTable = $sm->get('Administrador\Model\BrandTable');
}
return $this->brandTable;
}
}
My case was I passed wrong input filter. isValid returns false, but $form->getMessages() is empty. Form OrderForm had the following:
$form->setInputFilter(new \Application\Form\UserInputFilter($er));
When I changed UserInputFilter to OrderInputFilter it works.
Well, I got the answer :D
This is how the addAction should be:
public function addAction()
{
$formManager = $this->serviceLocator->get('FormElementManager');
$form = $formManager->get('Administrador\Form\ProductForm');
$form->get('submit')->setValue('Add');
$product = new Product();
$product->brand = new Brand();
$form->bind($product); // I need to bind the product to the form to pass the isValid() validation
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($product->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$product = $form->getData();
$this->getProductTable()->saveProduct($product);
// Redirect to list of products
return $this->redirect()->toRoute('product');
}
}
return new ViewModel(array(
'form' => $form,
));
}
Apparently i needed to bind and empty product object to the form to be able to pass the isValid() validation. After that i retrieve a product object from the $form->getData().
You can also do: $form->setBindOnValidate(false);

Implement change password in Symfony2

What is the best way to implement change password functionality in Symfony2?
Right now I'm using this:
$builder->add('password', 'repeated', array(
'first_name' => 'New password',
'second_name' => 'Confirm new password',
'type' => 'password'
));
It should also contain the current password check for security reasons.
Note: I'm not using FOSUserBundle.
Since Symfony 2.3 you can easily use UserPassword validation constraint.
Acme\UserBundle\Form\Model\ChangePassword.php
namespace Acme\UserBundle\Form\Model;
use Symfony\Component\Security\Core\Validator\Constraints as SecurityAssert;
use Symfony\Component\Validator\Constraints as Assert;
class ChangePassword
{
/**
* #SecurityAssert\UserPassword(
* message = "Wrong value for your current password"
* )
*/
protected $oldPassword;
/**
* #Assert\Length(
* min = 6,
* minMessage = "Password should be at least 6 chars long"
* )
*/
protected $newPassword;
}
Acme\UserBundle\Form\ChangePasswordType.php
namespace Acme\UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('oldPassword', 'password');
$builder->add('newPassword', 'repeated', array(
'type' => 'password',
'invalid_message' => 'The password fields must match.',
'required' => true,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\UserBundle\Form\Model\ChangePassword',
));
}
public function getName()
{
return 'change_passwd';
}
}
Acme\UserBundle\Controller\DemoController.php
namespace Acme\UserBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Acme\UserBundle\Form\ChangePasswordType;
use Acme\UserBundle\Form\Model\ChangePassword;
class DemoController extends Controller
{
public function changePasswdAction(Request $request)
{
$changePasswordModel = new ChangePassword();
$form = $this->createForm(new ChangePasswordType(), $changePasswordModel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// perform some action,
// such as encoding with MessageDigestPasswordEncoder and persist
return $this->redirect($this->generateUrl('change_passwd_success'));
}
return $this->render('AcmeUserBundle:Demo:changePasswd.html.twig', array(
'form' => $form->createView(),
));
}
}
You have to either create another model with two fields:
one for the current password;
and the other for the new one.
Or add a non-persisted property to your user model like the FOSUserBundle does (see the plainPassword property).
So once you checked both current and new password are valid, you encode the new password and replace the old one with it.
Just add this to your form type:
$builder->add('oldPlainPassword', \Symfony\Component\Form\Extension\Core\Type\PasswordType::class, array(
'constraints' => array(
new \Symfony\Component\Security\Core\Validator\Constraints\UserPassword(),
),
'mapped' => false,
'required' => true,
'label' => 'Current Password',
));
I use a action from my controller:
public function changepasswordAction(Request $request) {
$session = $request->getSession();
if($request->getMethod() == 'POST') {
$old_pwd = $request->get('old_password');
$new_pwd = $request->get('new_password');
$user = $this->getUser();
$encoder = $this->container->get('security.encoder_factory')->getEncoder($user);
$old_pwd_encoded = $encoder->encodePassword($old_pwd, $user->getSalt());
if($user->getPassword() != $old_pwd_encoded) {
$session->getFlashBag()->set('error_msg', "Wrong old password!");
} else {
$new_pwd_encoded = $encoder->encodePassword($new_pwd, $user->getSalt());
$user->setPassword($new_pwd_encoded);
$manager = $this->getDoctrine()->getManager();
$manager->persist($user);
$manager->flush();
$session->getFlashBag()->set('success_msg', "Password change successfully!");
}
return $this->render('#adminlte/profile/change_password.html.twig');
}
return $this->render('#adminlte/profile/change_password.html.twig', array(
));
}
Can't You get old password from User before binding form?
// in action:
$oldpassword = $user->getPassword();
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
{
// check password here (by hashing new one)