Zend_Form using subforms getValues() problem - zend-framework

I am building a form in Zend Framework 1.9 using subforms as well as Zend_JQuery being enabled on those forms. The form itself is fine and all the error checking etc is working as normal. But the issue I am having is that when I'm trying to retrieve the values in my controller, I'm receiving just the form entry for the last subform e.g.
My master form class (abbreviated for speed):
Master_Form extends Zend_Form
{
public function init()
{
ZendX_JQuery::enableForm($this);
$this->setAction('actioninhere')
...
->setAttrib('id', 'mainForm')
$sub_one = new Form_One();
$sub_one->setDecorators(... in here I add the jQuery as per the docs);
$this->addSubForm($sub_one, 'form-one');
$sub_two = new Form_Two();
$sub_two->setDecorators(... in here I add the jQuery as per the docs);
$this->addSubForm($sub_two, 'form-two');
}
}
So that all works as it should in the display and when I submit without filling in the required values, the correct errors are returned. However, in my controller I have this:
class My_Controller extends Zend_Controller_Action
{
public function createAction()
{
$request = $this->getRequest();
$form = new Master_Form();
if ($request->isPost()) {
if ($form->isValid($request->getPost()) {
// This is where I am having the problems
print_r($form->getValues());
}
}
}
}
When I submit this and it gets past isValid(), the $form->getValues() is only returning the elements from the second subform, not the entire form.

I recently ran into this problem. It seems to me that getValues is using array_merge, instead of array_merge_recursive, which does render to correct results. I submitted a bug report, but have not gotten any feedback on it yet.
I submitted a bug report (http://framework.zend.com/issues/browse/ZF-8078). Perhaps you want to vote on it?

I think that perhaps I must have been misunderstanding the way that the subforms work in Zend, and the code below helps me achieve what I wanted. None of my elements share names across subforms, but I guess this is why Zend_Form works this way.
In my controller I now have:
if($request->isPost()) {
if ($form->isValid($request->getPost()) {
$all_form_details = array();
foreach ($form->getSubForms() as $subform) {
$all_form_details = array_merge($all_form_details, $subform->getValues());
}
// Now I have one nice and tidy array to pass to my model. I know this
// could also be seen as model logic for a skinnier controller, but
// this is just to demonstrate it working.
print_r($all_form_details);
}
}

I have a same problem to get value from subforms I solve it with this but not my desire one
code:
in controller i get value with this code that 'rolesSubform' is my subform name
$this->_request->getParam ( 'rolesSubform' );

Encountered the same problem. Used post instead of getValues.
$post = $this->getRequest()->getPost();
There are times when getValues does not return the same values returned by $post.
Must be a getValues() bug.

Related

ZEND: Displaying form error messages on failed validation

I have a form say:
class Application_Form_UserDetails extends Zend_Form
{
public function init()
{
$pswd = new Zend_Form_Element_Password('password');
$pswd->setLabel('New password:');
$pswd->setAttrib('size', 25);
$pswd->setRequired(false);
$pswd->addValidator('StringLength', false, array(4,15));
$pswd->addErrorMessage('Wron password');
}
}
In my user details controller class I have:
class UserDetailsController extends Zend_Controller_Action {
public function editAction()
{
$userId = $this->userInfo->id;
$DbTableUsers = new Application_Model_DbTable_User;
$obj = $DbTableUsers->getUserDetails($userId);
$this->view->formUser = new $this->_UserDetails_form_class;
$this->view->formCompany = new $this->_CompanyDetails_form_class;
if ($obj) {
$this->view->formUser->populate($obj);
}
$url = $this->view->url(array('action' => 'update-user-details'));
$this->view->formUser->setAction($url);
}
public function updateUserDetailsAction()
{
$formUser = new $this->_UserDetails_form_class;
if ($formUser->isValid($this->getRequest()->getPost())) {
}
else {
//validation failed
$formUser->markAsError();
$this->view->formUser = $formUser;
$this->_helper->redirector('edit', 'user-details');
}
}
}
The first time Edit action is called the form built and displayed.
User fills the form and sends it (updateUserDetailsAction is called).
In updateUserDetailsAction, on validation failure I mark the form as having errors and want to display the form with error messages that I previously set in updateUserDetailsAction class.
Then I redirect:
$this->_helper->redirector('edit', 'user-details');
in order to display the same form but with errors for the user to re-enter correct values.
The problem is I don't know how to let know the edit action that the form must display validation errors?
On $this->_helper->redirector('edit', 'user-details'); the form is redisplayed
as a new form with cleared erros but I need them displayed.
Do I do this the correct way?
regards
Tom
Problem comes from the fact that you are redirecting and in each method you are creating a new instance of the form, that means the form class is loosing its state - data you injected from the request and any other values passed to this object.
Combine editAction and updateUserDetailsAction into one method:
...
$formUser = new Form();
// populate the form from the model
if ($this->getRequest()->isPost()) {
if ($formUser->isValid($this->getRequest()->getPost())) {
// update the model
}
}
...
and have the form being submitted to the edit action. This will simplify your code and remove code duplication.
If you just wan to fix your code you can instantiate the form object in the init() method of your controller as set it as a property of your controller. This will way you will reuse same instance after redirection. I still think that solution above is much more compact and easier to understand for someone else.

Agiletoolkit: Autocomplete/Plus error: null model

I am trying to use the autocomplete addon in agile toolkit (I am still very much new to this, but it seems to be very well suited for my needs). Autocomplete Basic works, but when I use Plus and press the plus-button I get an error connected to no model set. In the Plus source the self-model should be used - but I don't understand how I should set the model of the autocomplete form.
This is the important part of the stack trace, I think:
Frontend_page_form: Form->setModel(Null)
Frontend_createquestions_form_question_id:
autocomplete\Form_Field_Plus->autocomplete{closure}(Object(Page))
This is my model:
class Model_QuestionInCollection extends Model_Table {
public $entity_code='questionincollection';
function init(){
parent::init();
$this->hasOne('Question')->display(array('form'=>'autocomplete/Plus'));
This is the code:
$form=$this->add('Form');
$form->setModel('QuestionInCollection');
--- EDIT
I ended up changing the model in autocomplete, and now it works, showing that something is wrong in the original "$self->model" - but of course it cannot be generalized. I did some extra changes (to make the new record show up in the autocomplete-field), so Autocomplete/Plus now is like this:
<?php
namespace autocomplete;
class Form_Field_Plus extends Form_Field_Basic
{
function init()
{
parent::init();
$self = $this;
$f = $this->other_field;
// Add buttonset to name field
$bs = $f->afterField()->add('ButtonSet');
// Add button - open dialog for adding new element
$bs->add('Button')
->set('+')
->add('VirtualPage')
->bindEvent('Add New Record', 'click')
->set(function($page)use($self) {
$model=$this->add('Model_Question');
$form = $page->add('Form');
$form->setModel($model); //Was: $self->model
//Would be nice if it worked...: $form->getElement($model->title_field)->set($self->other_field->js()->val());
if ($form->isSubmitted()) {
$form->update();
$js = array();
$js[] = $self->js()->val($form->model[$model->id_field]);
$js[] = $self->other_field->js()->val($form->model[$model->title_field]);
$form->js(null, $js)->univ()->closeDialog()->execute();
}
});
}
}
Here is an example on using autocomplete: http://codepad.demo.agiletech.ie/interactive-views/autocomplete
You can manually create a form and link the field with Model.
If that works fine, you can move on to trying and get your own example working. It seems OK, and should work in theory.

ZF2 layout form set input filters and validation

For my application in Zend Framework 2 I created a 'search form' in my layout.phtml.
I did this in my Application module by adding the search form as an variable.
(Form location Application/src/Application/Form/SearchForm.php)
Application/Module.php:
public function setFormToView($e)
{
$searchForm = new SearchForm();
$viewModel = $e->getViewModel();
$viewModel->setVariables(array(
'searchForm' => $searchForm,
));
}
The form directs to an Action in another module, here I want to handle what to do with
the incoming query.
MyModule/src/MyModule/Controller/MyModuleController.php
public function dataAction()
{
$form = new SearchForm();
$form->get('submit')->setValue('Add');
$website = $this->params()->fromQuery('search');
return array('searchForm', $form);
}
Getting the query 'search' is no problem, it works well. No I want to use my inputFilter I created under 'Application/src/Application/Model/Search.php'.
I tried to add this in my 'dataAction()' but no results, but I even can't change the submit value as I tried in my example above.
How do I set this in the right way in this situation? Or else, what is the right situation to handle a search form in layout.phtml.
Thanks in advance,
Nick

Symfony: How to hide form fields from display and then set values for them in the action class

I am fairly new to symfony and I have 2 fields relating to my table "Pages"; created_by and updated_by. These are related to the users table (sfGuardUser) as foreign keys. I want these to be hidden from the edit/new forms so I have set up the generator.yml file to not display these fields:
form:
display:
General: [name, template_id]
Meta: [meta_title, meta_description, meta_keywords]
Now I need to set the fields on the save. I have been searching for how to do this all day and tried a hundred methods. The method I have got working is this, in the actions class:
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form_params = $request->getParameter($form->getName());
$form_params['updated_by'] = $this->getUser()->getGuardUser()->getId();
if ($form->getObject()->isNew()) $form_params['created_by'] = $this->getUser()->getGuardUser()->getId();
$form->bind($form_params, $request->getFiles($form->getName()));
So this works. But I get the feeling that ideally I shouldnt be modifying the web request, but instead modifying the form/object directly. However I havent had any success with things like:
$form->getObject()->setUpdatedBy($this->getUser()->getGuardUser());
If anyone could offer any advice on the best ways about solving this type of problem I would be very grateful.
Thanks,
Tom
After processing and saving the form you could set those fields on the object and re-save:
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()));
if ($form->isValid())
{
$page = $form->save();
$user = $this->getUser()->getGuardUser();
$page->setUpdatedBy($user);
if (empty($page->created_by))
{
$page->setCreatedBy($user);
}
$page->save();
$this->getUser()->setFlash('notice', 'Successfully saved page.');
$this->redirect('#homepage');
}
}
There's also a Doctrine extension called Blameable that automatically sets edited_by and created_by fields on specified models. The Doctrine website is undergoing some reorganization but here is the cached page for the extension.
To process your form create a new object, set the fields then save.
$article = new Article();
$article->setName($request->getParameter($form->getName());
$article->setDescription($request->getParameter($form->getDescription());
$article->setMetaKeywords($request->getParameter($form->getMetaKeywords());
$article->save();
What you want to do is customize your form and unset the 'created_at' and 'updated_at' pieces of the form in configure
class SampleForm extends BaseSampleForm
{
public function configure()
{
unset(
$this['created_at'],
$this['updated_at']
);
}
}
Then they won't show up in the form and will get the values setup by the "Timestampable" behavior before being saved
http://stereointeractive.com/blog/2010/04/07/symfony-forms-hide-created_at-updated_at-columns/

ZEND Controllers -- How to call an action from a different controller

I want to display a page that has 2 forms. The top form is unique to this page, but the bottom form can already be rendered from a different controller. I'm using the following code to call the action of the other form but keep getting this error:
"Message: id is not specified"
#0 .../library/Zend/Controller/Router/Rewrite.php(441): Zend_Controller_Router_Route->assemble(Array, true, true)
My code:
First controller:
abc_Controller
public function someAction()
{
$this->_helper->actionStack('other','xyz');
}
Second controller:
xyz_Controller
public function otherAction()
{
// code
}
Desired results:
When calling /abc/some, i want to render the "some" content along with the xyz/other content. I think I followed the doc correctly (http://framework.zend.com/manual/en/zend.controller.actionhelpers.html) but can't find any help on why that error occurs. When I trace the code (using XDebug), the xyz/other action completes ok but when the abc/some action reaches the end, the error is thrown somewhere during the dispatch or the routing.
Any help is greatly appreciated.
You can accomplish this in your phtml for your someAction. So in some.phtml put <?php echo $this->action('other','xyz');?> this will render the form found in the otherAction of XyzController
The urge to do something like this is an indication you're going about it in totally the wrong way. If you have the urge to re-use content, it should likely belong in the model. If it is truly controller code it should be encapsulated by an action controller plugin
In phtml file u can use the $this->action() ; to render the page and that response would be added to current response ..
The syntax for action is as follows::
public function action($action, $controller, $module = null, array $params = array())
You can create new object with second controller and call its method (but it`s not the best way).
You can extend your first controller with the second one and call $this->methodFromSecond(); - it will render second form too with its template.
BTW - what type of code you want to execute in both controllers ?
Just an update. The error had absolutely nothing to do with how the action was being called from the second controller. It turns out that in the layout of the second controller, there was a separate phtml call that was throwing the error (layout/abc.phtml):
<?php echo $this->render('userNavigation.phtml') ?>
line of error:
echo $this->navigation()->menu()->renderMenu(...)
I'll be debugging this separately as not to muddy this thread.
Thanks to Akeem and hsz for the prompt response. I learned from your responses.
To summarize, there were 3 different ways to call an action from an external controller:
Instantiate the second controller from the first controller and call the action.
Use $this->_helper->actionStack
In the phtml of the first controller, action('other','xyz');?> (as Akeem pointed out above)
Hope this helps other Zend noobs out there.
Hm I can't find and idea why you need to use diffrent Controlers for one view. Better practise is to have all in one Controller. I using this like in this example
DemoController extends My_Controller_Action() {
....
public function indexAction() {
$this->view->oForm = new Form_Registration();
}
}
My_Controller_Action extends Zend_Controller_Action() {
public function init() {
parent::init();
$this->setGeneralStuf();
}
public function setGeneralStuf() {
$this->view->oLoginForm = new Form_Login();
}
}
This kind of route definition:
routes.abc.route = "abc/buy/:id/*"
routes.abc.defaults.controller = "deal"
routes.abc.defaults.action = "buy"
routes.abc.reqs.id = "\d+"
requires a parameter in order to function. You can do this with actionStack but you can also specify a default id in case that none is provided:
$this->_helper->actionStack('Action',
'Controller',
'Route',
array('param' => 'value')
);
routes.abc.defaults.id = "1"
For Me this worked like a charm
class abcController extends Zend_Controller_Action
{
public function dashBoardAction()
{
$this->_helper->actionStack('list-User-Data', 'xyz');
}
}
class XyzController extends Zend_Controller_Action {
public function listUserDataAction()
{
$data = array('red','green','blue','yellow');
return $data;
}
}