Symfony2 checkbox form, alter date based on a checkbox - forms

For test purposes I make a light library online tool with Symfony2.
I have a Borrow entity that links to other entities. I want to create a form to validate the return of the books a given user have borrowed.
I have gathered all the Borrow recordings that correspond to books the user has not returned.
Now I want to create a form with a checkbox list to validate the books the user has returned.
After that, I can make something like what follows (which is not the problem).
if ($this->getRequest()->getMethod() == 'POST')
{
$form->bindRequest($this->getRequest());
if ($form->isValid())
{
$obj_list = $form->getData();
foreach ($obj_list as $obj)
{
$obj->setBorrowEndDate(...); // Today's datetime
$em = $this->getDoctrine()->getEntityManager();
$em->persist($obj);
$em->flush();
}
return $this->redirect($this->generateUrl('index'));
}
else
{
$args['problem']=$form->errors();
}
}
// Continuation : creation of the form

instead of creating entity attribute, you can do it in this way:
$builder->add('check', 'checkbox', [
'mapped' => false
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event)
{
$form = $event->getForm();
$object = $event->getData();
if($form->get('check')->getData() && !$object->getBorrowEndDate()) {
$object->setBorrowEndDate(new \DateTime());
}
$event->setData($object);
});

I don't really understand your problem but i'll try to help you:
1) Create a class attribute in your Entity (Borrow) called "check" for example, which is not depending on your databases:
private $check;
public function setCheck($check)
{
$this->check = $check;
}
public function getCheck()
{
return $this->check;
}
2) Add it in your class form as a checkbox input.
$builder->add('check', 'checkbox')
3) Then, you've got 2 solutions:
Update its setter like this:
public function setCheck($check)
{
$this->check = $check;
// Update borrow endtime
if ($this->check === true && !$this->getBorrowEndDate()) {
$this->setBorrowEndDate(new \DateTime());
}
}
Or use doctrine lifecycles:
/**
* Called before persisting your entity
*
* #ORM\prePersist
*/
public function prePersist()
{
// Update borrow endtime
if ($this->check === true && !$this->getBorrowEndDate()) {
$this->setBorrowEndDate(new \DateTime());
}
}
Hope this helps you !

Related

How to pass variable from postPersist Event listener to Controller

I have implemented an EventListener class and declare it in services.yaml
I'd like to return to my Controller a variable when entity is persited and send this variable to twig template. I want to show a step form in my view showing Entity name in green for example when data has been persisted in database. If it works I will use the same process in another controller where I persist multiple entities. To sum up: How to notify a controller that a specific entity has been persisted by passing a variable?
The eventlistener
<?php
namespace App\EventListener;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use App\Entity\Article;
class TodoListener {
public function postPersist(LifecycleEventArgs $args) {
$entity = $args->getObject();
if(!$entity instanceof Article)
return;
$var = 'foo';
return $var;
}
}
services.yaml
App\EventListener\TodoListener:
tags:
- { name: doctrine.event_listener, event: postPersist }
Controller
/**
* #Route("/blog/new", name="blog_create")
* #Route("/blog/{id}/edit", name="blog_edit")
*/
public function form(Article $article = null, Request $request, ObjectManager $manager)
{
if (!$article) {
$article = new Article();
}
$form = $this->createForm(ArticleType::class, $article);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if (!$article->getId()) {
$article->setCreatedAt(new \dateTime());
}
$manager->persist($article);
$manager->flush();
/**
* Get back variable when entity is persisted ???
*/
return $this->redirectToRoute('blog_show', ['id' => $article->getId()]);
}
return $this->render('blog/create.html.twig', [
'formArticle' => $form->createView(),
'editMode' => $article->getId() !== null
]);
}
In short: you can’t.
You can try to work around it with a custom symfony event, but is very bad.
If you want to know if an entity is new or already persisted you should call getEntityState on entity manager’s UnitOfWork or split the flows between actions (write two distinct actions for new and edit).
Anyway, just a suggestion: set the createdAt field into the entity constructor ;)

ContentEntityForm buildForm method parent out of memory

I have the following issue with my form class that extends the ContentEntityForm class.
When calling the parent buildForm which is needed my system runs out of memory.
/**
* {#inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
// Here is already runs of memory. $form is never initiated.
/* #var $entity \Drupal\sg_configuration_rule\Entity\ConfigurationRule */
$entity = $this->entity;
$form_state->set('old_cron_value', $entity->get('cron_settings')->first()->value);
$type = FALSE;
if (!$entity->isNew()) {
$type = $entity->getPluginInstance()->getPluginId();
}
if ($entity->isNew()) {
$type = \Drupal::request()->query->get('type');
if (!$type) {
return new RedirectResponse(Url::fromRoute('configuration_rule.add_form_step1')->toString());
}
}
if ($type) {
try {
/** #var \Drupal\sg_base_api\Plugin\BaseApiPluginInterface $enabled_api */
$enabled_api = $this->baseApiPluginManager->createInstance($type);
}
catch (PluginException $exception) {
LoggerService::error($exception->getMessage());
return new RedirectResponse(Url::fromRoute('configuration_rule.add_form_step1')->toString());
}
$enabled_api->configRuleForm($form, $entity);
$form['plugin_type']['widget'][0]['value']['#value'] = $type;
$form['plugin_type']['widget'][0]['value']['#access'] = FALSE;
$form['plugin_type']['widget'][0]['value']['#disabled'] = TRUE;
$form['server_node']['widget']['#options'] = $this->getServerNodesByType($enabled_api->entityType());
}
$form['user_id']['#access'] = FALSE;
return $form;
}
When i check the parent function i noticed that the line:
$form = $this->form($form, $form_state); is causing this in the class EntityForm(Core method).
/**
* {#inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// During the initial form build, add this form object to the form state and
// allow for initial preparation before form building and processing.
if (!$form_state->has('entity_form_initialized')) {
$this->init($form_state);
}
// Ensure that edit forms have the correct cacheability metadata so they can
// be cached.
if (!$this->entity->isNew()) {
\Drupal::service('renderer')->addCacheableDependency($form, $this->entity);
}
// Retrieve the form array using the possibly updated entity in form state.
// This is causing my memory timeout.
$form = $this->form($form, $form_state);
// Retrieve and add the form actions array.
$actions = $this->actionsElement($form, $form_state);
if (!empty($actions)) {
$form['actions'] = $actions;
}
return $form;
}
If i comment that line out it is working fine but this is needed to save my values in config. Also this is core and should work.
Anyone else have this problem and knows the solutions to this?
Thanks.
This is solved, the error was that too much results where loaded in a select field.

Symfony 4 Form handling

I am not quite sure how symfony is providing the possibility of different forms for different users/views.
For my understanding you have the action:
public function new(Request $request): Response
{
$order = new Orders();
$form = $this->createForm(OrdersType::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($order);
$em->flush();
return $this->redirectToRoute('orders_index');
}
return $this->render('orders/new.html.twig', [
'order' => $order,
'form' => $form->createView(),
]);
}
Which is generating the form with the, in this case OrdersType. And then the view gets loaded in the new.html.twig file. But some users only are allowed on the action "add" which should not show the option of calculated price or what ever.
How do I do that?
The solution depends of the structure of your application.
If the calculation price is in the new.html.twig you can use another file for users with limited access.
In the example below "ROLE_RESTRICTED" is the role of users allowed only to add and not to see the price.
For example :
public function new(Request $request): Response
{
$logged_user = $this->get('security.token_storage')->getToken()->getUser();
$order = new Orders();
$form = $this->createForm(OrdersType::class, $order);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($order);
$em->flush();
return $this->redirectToRoute('orders_index');
}
if($logged_user->hasRole('ROLE_RESTRICTED')){
$view = 'orders/newRestricted.html.twig'
}else{
$view = 'orders/new.html.twig';
}
return $this->render($view , [
'order' => $order,
'form' => $form->createView(),
]);
}
If the difference are small between new.html.twig and newRestricter.html.twig you can keep only one twig file and just make some conditional zones :
{% if not app.user.hasRole('ROLE_RESTRICTED') %}
Edit an order
{% else %}
Do not forget to secure the route corresponding to avoir direct URL access (in controller) :
public function edit($id)
{
$logged_user = $this->get('security.token_storage')->getToken()->getUser();
if($logged_user->has_role('ROLE_RESTRICTED')) {
throw $this->createAccessDeniedException('Access denied');
}
// create form, return render, etc.
}
In fact you have to restrict access to the page or part of the views.
You can find more details on the security section on the official doc :
https://symfony.com/doc/current/security.html
Another solution would be to make the filter in the form builder :
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class OrdersType extends AbstractType
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$logged_user = $this->tokenStorage->getUser();
if(!$logged_user->hasRole('ROLE_RESTRICTED')){
$builder->add('MyField', TextType::class,array(
'required' => false
));
}
}
...

zendframework 2 Doctrine 2 my post form is not returning the values

i am a little baffled by this;
my post forms is not populating the values received from the returned post values; i suspect the problem is arising from my getJobId() in my jobsort class values;
below is my form:
public function jobSortAction()
{
$form = new CreateJobSortForm($this->getEntityManager());
$jobSort = new JobSort();
$form->setInputFilter($jobSort->getInputFilter());
$id= 11;
$jobSort->setId($id);
$form->bind($jobSort);
if ($this->request->isPost()) {
//$post = $this->request->getPost();
$form->setData($this->request->getPost());
//var_dump($post);
//var_dump($jobSort);
if ($form->isValid()) {
$this->getEntityManager()->persist($jobSort);
$this->getEntityManager()->flush();
}
}
return array('form' => $form);
}
below is the var_dumped values of the 'return post values' and the Jobsort() object. You will note that the returned post values has values for both the Id and the JobId
object(Zend\Stdlib\Parameters)[168]
public 'JobSort' =>
array (size=2)
'jobId' => string '5' (length=1)
'id' => string '11' (length=2)
public 'submit' => string 'Submit' (length=6)
object(Workers\Entity\JobSort)[394]
protected 'inputFilter' => null
protected 'id' => int 11
protected 'jobId' => null
protected 'workerservicelist' => null
yet, when i populate the values, it does not seem to record the values for the jobId
below is my jobsort entity class:
class JobSort
{
protected $inputFilter;
/**
* #ORM\Id
*
* #ORM\Column(name="user_id", type="integer")
*/
protected $id;
/**
* #ORM\Column(name="jobId", type="integer")
*/
protected $jobId;
public function setId($id)
{
return $this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setJobId($jobId)
{
return $this->jobId = $jobId;
}
public function getJobId( )
{
return $this->jobId;
}
is there any advice or suggestions on what i need to do to find out why the values are not been populated
warm regards
Andreea
by the way; the form actually works when i had the Id of CLASS jobsort set to
#ORM\GeneratedValue(strategy="AUTO")
the problem started when i took it out and set it to manual
Hello again
here is my form:
this is the error message i received;
An exception occurred while executing 'INSERT INTO worker_main_jobsort (user_id, jobId) VALUES (?, ?)' with params [11, null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'jobId' cannot be null
here is my form:
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Form;
use Workers\Form\Fieldset\JobSortFieldset;
class CreateJobSortForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('create-Job-post-form');
// The form will hydrate an object of type "BlogPost"
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'));
// Add the user fieldset, and set it as the base fieldset
$JobSortFieldset = new JobSortFieldset($objectManager);
$JobSortFieldset->setUseAsBaseFieldset(true);
$this->add($JobSortFieldset);
// Optionally set your validation group here
// … add CSRF and submit elements …
$this->add(array(
'name' => 'submit',
'type' => 'Submit',
'attributes' => array(
'value' => 'Submit',
'id' => 'submitbutton',
),
));
// Optionally set your validation group here
}
}
and here is the fieldset class:
class JobSortFieldset extends Fieldset
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('JobSort');
$id= 10;
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'))
->setObject(new JobSort());
}
}
this addition is in response to rafaame solution;
i amended my form as recommended; however it still not working. i think the issue now is that Rafaame solution is in regarding to zendDB save method, but i am using doctrine persis**t and **flush method . i accordingly get the following error message;
Call to undefined method Workers\Entity\JobSort::save()
below is my amended form:
public function jobSortAction()
{
$form = new CreateJobSortForm($this->getEntityManager() );
$jobSort = new JobSort();
if($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid())
{
$entity = $form->getData();
$model = new JobSort();
$model->save($entity);
// $this->getEntityManager()->persist( $model);
// $this->getEntityManager()->flush();
}
}
return array('form' => $form);
}
in response to Rafaame question about what problems i had,the message that i am now receiving is this:
**
EntityManager#persist() expects parameter 1 to be an entity object,
array given.
**
below is my function:
public function jobSortAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getEntityManager();
$form = new CreateJobSortForm($this->getEntityManager());
if ($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid()) {
$entity = $form->getData();
$model = new JobSort($objectManager, $serviceLocator);
$model->getEntityManager()->persist($entity);
$model->getEntityManager()->flush();
}
}
return array('form' => $form);
}
my form; i.e where the hydrator should be set
namespace Workers\Form;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Form;
use Workers\Form\Fieldset\JobSortFieldset;
class CreateJobSortForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('JobSort');
// The form will hydrate an object of type "BlogPost"
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'));
// Add the user fieldset, and set it as the base fieldset
$JobSortFieldset = new JobSortFieldset($objectManager);
$JobSortFieldset->setUseAsBaseFieldset(true);
$this->add($JobSortFieldset);
If you check your code, you are creating a JobSort entity, setting only its id and binding it to the form:
$jobSort = new JobSort();
$jobSort->setId($id);
$form->bind($jobSort);
After that, you are dumping $jobSort and $this->request->getPost(). So, obviously, you are getting jobId in the POST data but not in the entity (you didn't set the entity's jobId before binding it to the form). There's nothing wrong with your entity's code.
The solution for this: don't bind anything to the form. You should only bind an entity to the form in the case of an edit action, that you fetch the entity from the database and want to populate the form with its values.
Example of add action:
public function addAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getObjectManager();
$form = new Form\EmailCampaign\Add($serviceLocator, $objectManager);
if($this->request instanceof HttpRequest && $this->request->isPost())
{
$form->setData($this->request->getPost());
if($form->isValid())
{
$entity = $form->getData();
//If you want to modify a property of the entity (but remember that it's not recommended to do it here, do it in the model instead).
//$entity->setJobId(11);
$model = new Model\EmailCampaign($serviceLocator, $objectManager);
$model->save($entity);
if($entity->getId())
{
$this->flashMessenger()->addSuccessMessage('Email campaign successfully added to the database.');
return $this->redirect()->toRoute('admin/wildcard', ['controller' => 'email-campaign', 'action' => 'edit', 'id' => $entity->getId()]);
}
else
{
$this->flashMessenger()->addErrorMessage('There was an error adding the email campaign to the database. Contact the administrator.');
}
}
}
return new ViewModel
([
'form' => $form,
]);
}
Example of edit action:
public function editAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getObjectManager();
$form = new Form\EmailCampaign\Edit($serviceLocator, $objectManager);
$id = $this->getEvent()->getRouteMatch()->getParam('id');
$entity = $objectManager
->getRepository('Application\Entity\EmailCampaign')
->findOneBy(['id' => $id]);
if($entity)
{
$form->bind($entity);
if($this->request instanceof HttpRequest && $this->request->isPost())
{
$form->setData($this->request->getPost());
if($form->isValid())
{
//If you want to modify a property of the entity (but remember that it's not recommended to do it here, do it in the model instead).
//$entity->setJobId(11);
$model = new Model\EmailCampaign($serviceLocator, $objectManager);
$model->save($entity);
$this->flashMessenger()->addSuccessMessage('Email campaign successfully saved to the database.');
}
}
}
else
{
$this->flashMessenger()->addErrorMessage('A email campaign with this ID was not found in the database.');
return $this->redirect()->toRoute('admin', ['controller' => 'email-campaign']);
}
return new ViewModel
([
'form' => $form,
'entity' => $entity,
]);
}
Hope this helps.
EDIT:
What I provided was an example of how to handle the form and the entities with Doctrine 2 + ZF2.
What you have to keep in mind is that Doctrine doesn't work with the concept of models, it just understands entities. The model I'm using in my application is a concept of the MVC (Model-View-Controller) design pattern (that ZF2 uses) and I have decided to wrap the entity manager calls (persist and flush) inside my model's method, that I named save() (in the case the entity needs some special treatment before being save to the database and also because it is not a good practice to use the entity manager directly in the controller - see this slide of Marcos Pivetta presentation http://ocramius.github.io/presentations/doctrine2-zf2-introduction/#/66).
Another thing that you may be misunderstanding is that when you do $form->getData() to a form that has the DoctrineObject hydrator, it will return you the entity object, and not an array with the data (this last happens if it has no hydrator). So you don't need to create the entity after doing $form->getData(), and if you do so, this created entity won't have any information provided by the form.
Your code should work now:
public function jobSortAction()
{
$serviceLocator = $this->getServiceLocator();
$entityManager = $this->getEntityManager();
$form = new CreateJobSortForm($entityManager);
if ($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid()) {
//I'm considering you are setting the DoctrineObject hydrator to your form,
//so here we will get the entity object already filled with the form data that came through POST.
$entity = $form->getData();
//Again, if you need special treatment to any data of your entity,
//you should do it here (well, I do it inside my model's save() method).
//$entity->setJobId(11);
$entityManager->persist($entity);
$entityManager->flush();
}
}
return array('form' => $form);
}

Zend ACL Dynamic Assertion

I want to restrict my users to edit/delete only the comments which they added. I found an example on youtube by a guy named intergral30 and followed his instruction. And now my admin account has the possibility to edit/delete everything, but my user has no access to his own comment.
Here's the code:
Resource
class Application_Model_CommentResource implements Zend_Acl_Resource_Interface{
public $ownerId = null;
public $resourceId = 'comment';
public function getResourceId() {
return $this->resourceId;
}
}
Role
class Application_Model_UserRole implements Zend_Acl_Role_Interface{
public $role = 'guest';
public $id = null;
public function __construct(){
$auth = Zend_Auth::getInstance();
$identity = $auth->getStorage()->read();
$this->id = $identity->id;
$this->role = $identity->role;
}
public function getRoleId(){
return $this->role;
}
}
Assertion
class Application_Model_CommentAssertion implements Zend_Acl_Assert_Interface
{
public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user=null,
Zend_Acl_Resource_Interface $comment=null, $privilege=null){
// if role is admin, he can always edit a comment
if ($user->getRoleId() == 'admin') {
return true;
}
if ($user->id != null && $comment->ownerId == $user->id){
return true;
} else {
return false;
}
}
}
In my ACL I have a function named setDynemicPermissions, which is called in an access check plugin's preDispatch method.
public function setDynamicPermissions() {
$this->addResource('comment');
$this->addResource('post');
$this->allow('user', 'comment', 'modify', new Application_Model_CommentAssertion());
$this->allow('admin', 'post', 'modify', new Application_Model_PostAssertion());
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$this->_acl->setDynamicPermissions();
}
And I'm calling the ACL-s isAllowed method from my comment model where I return a list of comment objects.
public function getComments($id){
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$commentResource = new Application_Model_CommentResource();
$comments = array();
foreach ($res as $comment) {
$commentResource->ownerId = $comment[userId];
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
if (Zend_Registry::get('acl')->isAllowed($userRole->getRoleId(), $commentResource->getResourceId(), 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
Can anyone tell me what have I done wrong?
Or what should I use if I want to give my admins the right to start a post and other users the right to comment on them. Each user should have the chance to edit or delete his own comment and an admin should have all rights.
You seem to be using the dynamic assertions in a wrong manner, as you are still passing the roleId to isAllowed().
What these dynamic assertions really do, is take a complete object and work with it. Zend will determine which rule has to be used by calling getResourceId() and getRoleId() on your objects.
So all you have to do is pass your objects instead of the strings to isAllowed():
public function getComments($id){
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$commentResource = new Application_Model_CommentResource();
$comments = array();
foreach ($res as $comment) {
$commentResource->ownerId = $comment[userId];
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
// This line includes the changes
if (Zend_Registry::get('acl')->isAllowed($userRole, $commentResource, 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
But in can be done better
You would not have to implement a total new Application_Model_CommentResource, but instead you can use your actual Application_Model_Comment like this:
// we are using your normal Comment class here
class Application_Model_Comment implements Zend_Acl_Resource_Interface {
public $resourceId = 'comment';
public function getResourceId() {
return $this->resourceId;
}
// all other methods you have implemented
// I think there is something like this among them
public function getOwnerId() {
return $this->ownerId;
}
}
Assertion would then use this object and retrieve the owner to compare it with the actually logged in person:
class Application_Model_CommentAssertion implements Zend_Acl_Assert_Interface {
public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user=null,
Zend_Acl_Resource_Interface $comment=null, $privilege=null){
// if role is admin, he can always edit a comment
if ($user->getRoleId() == 'admin') {
return true;
}
// using the method now instead of ->ownerId, but this totally depends
// on how one can get the owner in Application_Model_Comment
if ($user->id != null && $comment->getOwnerId() == $user->id){
return true;
} else {
return false;
}
}
And the usage is like this:
public function getComments($id) {
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$comments = array();
foreach ($res as $comment) {
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
// no $commentResource anymore, just pure $comment
if (Zend_Registry::get('acl')->isAllowed($userRole, $comment, 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}