I'm still learning about ZF3. I wanted to test different methods of nested views. First I tried partials, that works.
Now for comparisson reasons I wanted to do the same with a child-view. That doesn't work. Because I did the the same with the partial before, I'm really sure that my models work quite fine, so there is no problem, must be my code for adding the child.
Here ist my controlleraction (I left the partial variety toggled):
public function showtestAction()
{
$adapter = $this->authService->getAdapter();
if(!$this->authService->hasIdentity())
{
return $this->redirect()->toRoute('index', ['action' => 'index']);
}
else {
$parameter = $this->params()->fromRoute('id');
$part =substr($parameter,0,strpos($parameter,"-"));
$dclid=substr($parameter, strripos($parameter,"-")+1, strlen($parameter));
$test3=str_replace('-'.$dclid,'',$parameter);
$unit=substr($test3, strpos ($test3,"-")+1, strripos ($test3,"-")-1);
if (!empty($parameter)) {
$view= new ViewModel([
'dcls' => $this->table->fetchPartDcl($part,$dclid),
]);
// optional $view->setTemplate('import/pcb/showtest.phtml');
$padview= new ViewModel([
'pads'=> $this->padtable->fetchPadPart($part, $unit, $dclid)
]);
$padview->setTemplate('import/pcb/showpad.phtml');
$view->addChild($padview,'pads');
return $view;
//test partial
// return new ViewModel([
// 'dcls' => $this->table->fetchPartDcl($part,$dclid),
// 'pads' => $this->padtable->fetchPadPart($part, $unit, $dclid),
// ]);
//ende test partial
}
}
}
Problem is, the child doesn't work, it is not there. I added a h2 headline to control it.
My child template is saved under: module\Import\view\child\showpad.phtml
Where is my error? Any help appreciated!
in template of $view (import/pcb/showtest.phtml) add:
echo $this->pads;
Related
I cannot make my logic work when following the official Symfony docs here: https://symfony.com/doc/current/form/form_collections.html#allowing-tags-to-be-removed
Based on the example i need to get the originalTags and then compare them with the new tags after form has been handled.
In my case I have a Purchase entity, that can have a collection of PurchaseProducts(ManyToMany). In my case, when I change a PurchaseProduct I need to update the stock of the purchase that has been removed. However no matter how I get the original PurchaseProducts, after $form->handleRequest() they are updated with the new values and I lose any information about the original ones.
Fragments form my controller with the logic:
/** #var Purchase $purchase */
$purchase = $this->getDoctrine()
->getRepository(Purchase::class)
->find($id);
if (!$purchase) {
$this->addFlash('error', 'Purchase not found');
return $this->redirect($this->generateUrl('purchase_list'));
}
$originalProducts = new ArrayCollection();
foreach ($purchase->getPurchaseProducts() as $purchaseProduct) {
$originalProducts->add($purchaseProduct);
}
$form = $this->createForm(PurchaseType::class, $purchase);
if ($request->isMethod('POST')) {
dump($originalProducts); // Original entities are here
$form->handleRequest($request);
dump($originalProducts);die; // Original entities are updated with the new ones
...
// This will not work since originalProducts already has the new entities
foreach ($originalProducts as $purchaseProduct) {
if (false === $purchase->getPurchaseProducts()->contains($purchaseProduct)) {
// update stock here
}
}
I have tried many options, like cloning, querying the database and so on, but after handleRequest I always get the same updated entities. Why?
Explanation of Behavior
As you are referring to the "allow_delete" => true or "allow_add" => true concepts of the form processor. The entity property changes within the collection appearing in the copy when the Form is submitted is the expected behavior. However, the $originalProducts collection will NOT contain any new entities (new PurchaseProduct()).
This occurs because PHP passes the objects by-reference, namely the PurchaseProduct object to the ArrayCollection. Meaning any changes made to the embedded form object are applied to both the Purchase:::$purchaseProducts and the $originalProducts collections, since they are the same PurchaseProduct object (by-reference).
However, when they are removed from the Purchase:::$purchaseProducts collection after $form->handleRequest($request), the objects will still exist in the $originalProducts collection. Which allows for you to compare the two collections to remove them from the Entity Manager or your Entity collection in the event your Entity does not contain the necessary logic.
Example using ArrayObject and Foo objects: https://3v4l.org/oLZqO#v7.4.25
Class Foo
{
private $id;
public function __construct()
{
$this->id = uniqid('', false);
}
public function setId($id)
{
$this->id = $id;
}
}
//initial state of Purchase::$purchaseProducts
$foo1 = new Foo();
$foo2 = new Foo();
$a = new ArrayObject([$foo1, $foo2]);
//Create Copy as $originalProducts
$b = new ArrayObject();
foreach ($a as $i => $foo) {
$b->offsetSet($i, $foo);
}
//form submission
$foo1->setId('Hello World');
$a->offsetUnset(1);
Result
Initial state of Copy:
ArrayObject::__set_state(array(
0 =>
Foo::__set_state(array(
'id' => '6182c24b00a21',
)),
1 =>
Foo::__set_state(array(
'id' => '6182c24b00a28',
)),
))
-----------------------
Form Submitted ID Changed in Copy:
ArrayObject::__set_state(array(
0 =>
Foo::__set_state(array(
'id' => 'Hello World',
)),
1 =>
Foo::__set_state(array(
'id' => '6182c24b00a28',
)),
))
-----------------------
Source has foo2 entity removed:
ArrayObject::__set_state(array(
0 =>
Foo::__set_state(array(
'id' => 'Hello World',
)),
))
-----------------------
Copy still contains both entities:
ArrayObject::__set_state(array(
0 =>
Foo::__set_state(array(
'id' => 'Hello World',
)),
1 =>
Foo::__set_state(array(
'id' => '6182c24b00a28',
)),
))
Resolutions
Depending on your desired result, there are several approaches that can be used to detect and handle the changes, such as Change Tracking Policies.
Another way to determine what has changed for the entities, is the $em->getUnitOfWork()->getEntityChangeSet($entity) to retrieve the changes for an entity that was proposed to doctrine.
Code Suggestion
To ensure the request is not misinterpreted, you should always handle the form request (regardless of the request method) and verify that the form was submitted/valid. This is because the handleRequest method triggers multiple events depending on the request method.
/** #var Purchase $purchase */
$purchase = $this->getDoctrine()
->getRepository(Purchase::class)
->find($id);
if (!$purchase) {
$this->addFlash('error', 'Purchase not found');
return $this->redirect($this->generateUrl('purchase_list'));
}
$originalProducts = new ArrayCollection();
foreach ($purchase->getPurchaseProducts() as $purchaseProduct) {
$originalProducts->add($purchaseProduct);
}
$form = $this->createForm(PurchaseType::class, $purchase);
dump($originalProducts); // Original entities are here
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dd($originalProducts); // dump+die as dd() - Original entities are updated with the new ones
//...
// This will not work since originalProducts already has the new entities
foreach ($originalProducts as $purchaseProduct) {
if (false === $purchase->getPurchaseProducts()->contains($purchaseProduct)) {
//remove original from the Purchase entity
}
}
After tackling this other question we would now like to check if the authenticated user can view, update or delete an existing record. Since checkAccess() is called by default in all restActions the following seemed the most logic thing to try:
public function checkAccess($action, $model = null, $params = []) {
if(in_array($action, ['view', 'update', 'delete'])) {
if(Yii::$app->user->identity->customer->id === null
|| $model->customer_id !== Yii::$app->user->identity->customer->id) {
throw new \yii\web\ForbiddenHttpException('You can\'t '.$action.' this item.');
}
}
}
But the API seems to ignore this function. We added this function in our controller. The actions (view, update and delete) are the default restActions.
Our BaseController sets actions like this:
...
'view' => [
'class' => 'api\common\components\actions\ViewAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'scenario' => $this->viewScenario,
],
...
Are we forgetting something?
Just add the following inside your custom action before executing any other code as it was done in the default view action (see source code here):
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id, $model);
}
note: $this->checkAccess is defined in parent yii\rest\Action so your custom ActionView class need to either extend yii\rest\Action or redefine the variable public $checkAccess;
We obviously should have seen that the viewAction is not the default but an altered api\common\components\actions\ViewAction ... Not sure how we missed that...
I am using Zend Framework1.11. In my Zend Form I have two zend sub form, I have added these two sub form using addSubForm function.
Now when I call this zend form in controller then isValid function is not working. I have called it as follow..
public function registeredAction(){
$form = new Application_Form_RegisteredForm();
$form->setAction('registered');
$formData = $this->_request->getPost();
if($form->isValid($formData)){
// save into database using model class.
} else {
$form->populate($formData);
}
$this->view->form = $form;
}
In following code isValid is not working, while I print_r the $fotmData requested array, it print array like:-
Array(
[personal] => Array
(
[firstname] => 'Example',
[lastname] => 'Solution'
)
[MAX_FILE_SIZE] => 8388608
[address] => Array
(
[country] => 'IND',
[state] => 'RAJ'
)
);
I have also used the setData() function but it is not working, it's give exceptional error "Message: Method setData does not exist", I have used php array_merge function but return array is not working with isValid().
Can anyone help me to solve this problem. so I can easily store form data into database.
Thanks!
Take a look at array_merge
http://php.net/manual/de/function.array-merge.php
$newFormData=array_merge($formData["personal"],$formData["address"]);
My solution is to create new base form with new method getSubFormsValues(), i.e.:
class My_Form extends Zend_Form
{
public function getSubFormsValues()
{
$values = array();
foreach ($this->getSubForms() as $form) {
$name = $form->getName();
$value = $form->getValues();
$values = array_merge($value[$name], $values);
}
return $values;
}
}
When you can call $my_form_obj->getSubFormValues() in your code.
I have a form for the creation of new "groups". I now added a small "go back" image with which the user should be able to go back one step. I don't know why, but when I click this new image, the controller and action used for the form which I want to leave (/admin/creategroup) is called again with HTTP POST set. Therefore, the form validation is done, and I'm stuck at this form with the validation errors displayed.
This is a snippet of the code from my form with both image-buttons. I wan't the "go back"-image to redirect me to the specified controller without validating the form:
$this->addElement('image', 'btnBack', array (
'name' => 'btnBack',
'id' => 'btnBack',
'label' => '',
'title' => 'Go back',
'alt' => 'Go back',
'src' => '/img/undo.png',
'onClick' => "window.location='/admin/groupoverview'"
));
$this->addElement('image', 'btnSave', array (
'name' => 'btnSave',
'id' => 'btnSave',
'label' => '',
'title' => 'Save this new group',
'alt' => 'Save this new group',
'src' => '/img/save.png',
'onClick' => "document.forms[0].submit();"
));
Edit:
I already thought of the possibility to check in /admin/creategroup whether it was called from the 'btnBack'-image or the 'btnSave'-image and skip form validation and redirect correctly if the source was the 'btnBack'-image.
I just think that there should be a nicer solution to directly redirect from the form and circumvent calling /admin/creategroup again.
Edit2:
My view script:
<div id="createGroupMask">
<br/>
Use the form below to create a new group
<?php
$this->form->setAction($this->url());
echo $this->form;
?>
</div>
My action in the controller:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
Now there is something to work with:
The isValid() loop is incorrect, your form will never evaluate as inValid with respect to the elements you've presented, you will never get to the else.
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
/* This is incorrect */
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
My problem is that I'm not sure what is going to be submitted from your form, I'm not really familiar with how your using "onClick" and what I presume is javascript. It looks like element btnBack should redirect on click and element btnSave should POST. However this does not seem to be happening.
I have done this type of thing in PHP and ZF with submit buttons, perhaps the flow of what I did will help:
NOTE: for this type of flow to work you must give the button element a label. The label is used as the submit value.
//psuedoCode
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
//I would probably opt to perform this task with a switch loop
if ($form->getValue('btnBack') === some true value) {
$this->_redirect('new url');
}
if ($form->getValue('btnSave') === some true value) {
//Process and save data
}
} else {
//Display form errors
}
$this->view->form = $form;
}
I think when all is said and done the crux of your problem is that you did not give your button elements a label.
I tried adding labels to my images, but this didn't work.
I also tried to use the isChecked() method on my btnBack-image like this:
if ($form->btnBack->isChecked()) {
// 'Go back' image was clicked so this is no real error, just redirect
}
This didn't work either.
I finally was able to check which image was clicked via the following method as answered in Zend form: image as submit button:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
if (isset($this->_request->btnBack_x)) {
// 'Go back' image was pressed, so this is no error
// -> redirect to group overview page
$this->_redirect('/admin/groupoverview');
}
}
}
$this->view->form = $form;
}
I guess this doesn't thoroughly answer the original question as the validation is still done and I'm only checking for this 'special case' where the 'Go back' image was clicked, but I'll mark it as answered anyways.
Tim Fountain suggested an even cleaner approach in my somewhat related question:
Zend forms: How to surround an image-element with a hyperlink?
I have this form placed inside a block and it is assigned to right region of my site. Form is displayed just fine. But the submit button doesn't work as intended - to call submit function. So, I did some debugging and found an anomaly that there is no essential data - such as form_id and tokens - drupal normally injected to every form. As I can't figure out the root cause of this, I'm here for pointers of friends from here. Here's an excerpt of my code -
function mymodule_block($op = 'list', $delta = '', $edit = array()) {
switch ($op) {
case 'list':
$blocks['quick_search'] = array(
'info' => t('Quick Search'),
);
return $blocks;
case 'view':
switch ($delta) {
case 'quick_search':
$block['subject'] = t('Quick Search');
$block['content'] = drupal_get_form("block_quick_search");
break;
}
return $block;
}
}
function block_quick_search(&$form_state){
$form = array();
.
.
.
$form['quick_search_submit'] = array(
'#type' => 'submit',
'#value' => t('Search'),
'#submit' => array('mymodule_quick_search'),
);
return $form;
}
function mymodule_quick_search($form, &$form_state){
drupal_goto($base_path,"..............");
}
Thanks in advance
there is no essential data - such as form_id and tokens
This is indeed the reason why form submissions are not processed correctly. Check whether drupal_prepare_form is called on your form and whether it adds those items correctly. It is called by drupal_get_form if he form is not posted (and thusly not retrieved from the cache).
If $form['#token'] and $form['form_id'] are added correctly, I suspect something is wrong with translating the form to HTML. Do you use any custom theming for the form?
Try to pass your submit handler to the main form and not on the item submit like that:
$form['#submit'][] = 'mymodule_quick_search';
it should work.