I'm having a problem when I try to save the form's information into the database. My form doesn't seem to be valid, even after manually setting the Theater's id for every theater in the chosen Network.
Here's the related part of my module's actions.class.php :
Here's executeCreate():
public function executeCreate(sfWebRequest $request) {
$this->form = $this->configuration->getForm();
$this->showing = $this->form->getObject();
$this->processCreateForm($request, $this->form);
$this->setTemplate('new');
}
and now processCreateForm():
protected function processCreateForm(sfWebRequest $request, sfForm $form) {
$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
$form_name = $form->getName();
$parameters = $request->getParameter($form_name);
$network_id = $parameters['network_id'];
$theaters_list = Doctrine_Query::create()
[...]
->execute();
foreach ($theaters_list as $theater) {
$form->getObject()->setTheaterId($theater->theater_id);
$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
if ($form->isValid()) {
$showing = $form->save();
} else {
foreach ($form->getErrorSchema()->getErrors() as $key => $error) {
echo '<p>' . $key . ': ' . $error . '</p>';
}
}
}
$this->getUser()->setFlash('update_success', true);
$this->setTemplate('new');
}
Here's the output :
Thank you for your help
There are two strange things going on here which I think break your code.
You run the bind() method twice which might be resetting your values on the object.
I don't think that the getObject() method returns the object by reference.
So when you run:
$form->getObject()->setX($val);
$form->save();
then you update the field of the object returned by the form, but then save the original object which is still bound to the form.
Try doing something like this:
$myObject = $form->updateObject()->getObject();
$myObject->setX($value);
$myObject->save();
The updateObject() is important if you use the form to edit an existing object, not to create a new one. Without this you will get the old values of the object.
If you want to run it in a loop you can loop only the setting and saving part. So you would have something like this in your processCreateForm:
protected function processCreateForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
if ($form->isValid()) { //You can check the validity of your form at this point.
//Create $theatersList
...
$myObject = $form->updateObject();
foreach ($theatersList as $theater) {
$myObject->setTheaterId($theater->theater_id);
$showing = $myObject->save();
//Do something with $showing
}
} else {
//Print the errors.
}
}
Using this code you can unset the widget for theatre_id in your form as it should not be set by the user and does not have to be part of the form validation.
EDIT
Some changes to the code:
protected function processCreateForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
if ($form->isValid()) { //You can check the validity of your form at this point.
//Create $theatersList
...
$myObject = $form->updateObject();
$myObjectVars = $myObject->toArray();
foreach ($theatersList as $theater) {
$myNewObject = new SomeClass();
$myNewObject->fromArray($myObjectVars);
$myNewObject->setTheaterId($theater->theater_id);
$showing = $myNewObject->save();
//Do something with $showing
$myNewObject->free();
unset($myNewObject);
}
} else {
//Print the errors.
}
}
Related
I have a php file(register.php) with a public function register($data) where errors are validated.Then errors are counted and if no errors are found, validation is passed.
register.php:
class ARegister {
public function register($data) {
$user = $data['userData'];
//validate provided data
$errors = $this->validateUser($data);
if(count($errors) == 0) {
//first validation
}
}
public function validateUser($data, $botProtection = true) {
$id = $data['fieldId'];
$user = $data['userData'];
$errors = array();
$validator = new AValidator();
if( $validator->isEmpty($user['password']) )
$errors[] = array(
"id" => $id['password'],
"msg" => Lang::get('password_required')
);
return $errors;
}
The problem is, that I need to get this confirmation of validated data to my other php file (othervalidation.php) where I've made another validation:
othervalidation.php:
<?php
require 'register.php';
if ( !empty($action) ) {
switch ( $action ) {
case 'process_payment':
try {
$instance = new ARegister();
if($instance->validateUser($data, $errors)) {
throw new Exception('Validation error');
}
} catch (Exception $e) {
$status = false;
$message = $e->getMessage();
}
}
How can I send the result of $errors variable to my other validation (othervalidation.php)?
I looked at your new code design and here's the new problems I found.
First, in your register function, you use the errors variable as an integer while your validate function returns an array. You got two possibilities here.
You can change your register method to check out if your error array is empty like this:
if(empty($errors)) {
//first validation
}
Count is also valid, but I still prefer empty since it's syntactically clearer. Furthermore, the count function returns 1 if the parameter is not an array or a countable object or 0 if the parameter is NULL. As I said, it is a functional solution in your current case but, in some other contexts, it might cause you unexpected results.
Here in your method declaration, I see that you are expecting a boolean (botProtection).
public function validateUser($data, $botProtection = true) {
But you are supplying an errors parameter
if($instance->validateUser($data, $errors)) {
You don't provide me the declaration of the errors variable, but it is probably not matching the bot protection parameter your function is expecting. PHP is using lose typing, it is useful but, once again, you got to be careful for bugs hard to find. For public function, you should always make sure a way or another that the supplied parameter won't lead to code crash.
In your code, the data parameter seems to be an array. You can use parameter hinting to force the use of array like this:
public function register(array $data) {
public function validateUser(array $data, $botProtection = true) {
And even specific class (as if you where using "instance of" in a condition)
public function register(MyDataClass $data) {
public function validateUser(MyDataClass $data, $botProtection = true) {
Also, you're not even using the botProtection parameter in your validateUser method.
On the same function call:
if($instance->validateUser($data, $errors)) {
you are expecting a Boolean (true or false), but the method returns an array. If you want to use the code the way it is currently designed, you must use it like this
if(!empty($instance->validateUser($data, $errors)) {
Here, I'm not so sure it is necessary to use exception. Ain't it be easier to design your code like this?
if(!empty($instance->validateUser($data, $errors)) {
$message = 'Validation error';
}
In your validate function, is the "isEmpty" function also validating if the client provided a password?
If that's the case you could validate it like this:
if(!in_array($user['password']) or empty($user['password']))
With those corrections, your code should be functional.
Here's a sample of how I would had design your code (considering the code sample provided):
class ARegister {
public function register($data) {
$user = $data['userData']; //don't declare it here, all the user validations must be done in validateUser($data, &$errors)
$errors = array();
if($this->validateUser($data, $errors)) {
//first validation
}
}
/**
* Note: If you are not returing more than one error at the time, $errors should be a string instead of an array.
*/
public function validateUser($data, array &$errors) {
$isValid = false;
if (in_array($data['fieldId']) and in_array($data['fieldId']['password']) and in_array($data['userData'])){
if(!in_array($data['userData']['password']) or empty($data['userData']['password'])){
$errors[$data['fieldId']['password']] = Lang::get('password_required');
}
else{
$isValid = true;
}
}
else{
//an invalid data array had been provided
}
return $isValid;
}
For the next part, if the code is executed directly in the view and you are a beginner, create a procedural external controller file (all functions will be public...). If you are a professional, you MUST create a class to encapsulate the treatment.
You must not do treatment directly in the view. The view is a dumb placeholder for data presentation and collecting client's input. The sole action it must do is display the data sent by the controller and send back the client's input to the controller.
The treatment on data is the controller responsibility.
if (!empty($action) ) {
$errors =array();
switch ( $action ) {
case 'process_payment':
$instance = new ARegister();
if($instance->validateUser($data, $errors)) {
//the user is valid, do the treatment
}
else
PageManager::dispayError($errors);
}
unset($instance);
}
}
Here's an example how you can centralize your error display
/**
* Can be more complexe than that, but I'm at my father's home at four hundred kms away from Montreal right now..
*/
public static function dispayError($errors, $size = 4){
if (is_numeric($size)){
if ($size < 0){
$size = 1;
}
elseif($size > 5){
$size = 5;
}
}
else{
$size = 4;
}
if (is_scalar($errors)){
echo '<h' . $size . 'class="ERROR_MESSAGE">' . $errors . '</h' . $size . '><br>';
}
elseif (is_array($errors)){
foreach ($errors as $error){
if (is_scalar($error)){
echo '<h' . $size . 'class="ERROR_MESSAGE">' . $error . '</h' . $size . '><br>';
}
}
}
}
Of course, you can also support many kind of message:
public static function dispayError($errors, $size = 4){
self::displayMessage("ERROR_MESSAGE", $errors, $size=4);
}
private static displayMessage($class, $messages, $size=4)
Well, took me two hours to write that. I hope you have now enough material to build an efficient, reusable and, no less important, safe code design.
Good success,
Jonathan Parent-Lévesque from Montreal
You can try something like this:
class ARegister {
private $error = 0;
public function register($data) {
if (!$this->validateUser($data)){
$this->error++;
}
}
public function getErrorCount(){
return $this->error;
}
public resetErrorCount(){
$this->error = 0;
}
Or pass the error by reference:
public function register(&$error, $data) {
if (!$this->validateUser($data)){
$error++;
}
}
Personally, I would do all the validation in the same method (in the class for encapsulation), use an error message parameter (passed by reference) to return why the validation failed and use the return statement to return true or false.
class MyClass{
public function validation(&$errorMessage, $firstParameter, $secondParameter){
$success = false;
if (!$this->firstValidation($firstParameter)){
$errorMessage = "this is not working pal.";
}
elseif (!this->secondeValidation($firstParameter)){
$errorMessage = "Still not working buddy...";
}
else{
$success = true;
}
return $success;
}
private function firstValidation($firstParameter){
$success = false;
return $success;
}
private function secondeValidation($secondParameter){
$success = false;
return $success;
}
}
In your other file:
<?php
$instance = new MyClass();
$errorMessage = "";
if ($instance->validation($errorMessage, $firstParameter, $secondParameter)){
echo "Woot, it's working!!!";
}
else{
echo $errorMessage;
}
?>
Is one of these code solutions fit your needs?
Jonathan Parent-Lévesque from Montreal
I want to create a webservice to which I submit a form, and in case of errors, returns a JSON encoded list that tells me which field is wrong.
currently I only get a list of error messages but not an html id or a name of the fields with errors
here's my current code
public function saveAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new TaskType(), new Task());
$form->handleRequest($request);
$task = $form->getData();
if ($form->isValid()) {
$em->persist($task);
$em->flush();
$array = array( 'status' => 201, 'msg' => 'Task Created');
} else {
$errors = $form->getErrors(true, true);
$errorCollection = array();
foreach($errors as $error){
$errorCollection[] = $error->getMessage();
}
$array = array( 'status' => 400, 'errorMsg' => 'Bad Request', 'errorReport' => $errorCollection); // data to return via JSON
}
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
}
this will give me a response like
{
"status":400,
"errorMsg":"Bad Request",
"errorReport":{
"Task cannot be blank",
"Task date needs to be within the month"
}
}
but what I really want is something like
{
"status":400,
"errorMsg":"Bad Request",
"errorReport":{
"taskfield" : "Task cannot be blank",
"taskdatefield" : "Task date needs to be within the month"
}
}
How can I achieve that?
I am using this, it works quiet well:
/**
* List all errors of a given bound form.
*
* #param Form $form
*
* #return array
*/
protected function getFormErrors(Form $form)
{
$errors = array();
// Global
foreach ($form->getErrors() as $error) {
$errors[$form->getName()][] = $error->getMessage();
}
// Fields
foreach ($form as $child /** #var Form $child */) {
if (!$child->isValid()) {
foreach ($child->getErrors() as $error) {
$errors[$child->getName()][] = $error->getMessage();
}
}
}
return $errors;
}
I've finally found the solution to this problem here, it only needed a small fix to comply to latest symfony changes and it worked like a charm:
The fix consists in replacing line 33
if (count($child->getIterator()) > 0) {
with
if (count($child->getIterator()) > 0 && ($child instanceof \Symfony\Component\Form\Form)) {
because, with the introduction in symfony of Form\Button, a type mismatch will occur in serialize function which is expecting always an instance of Form\Form.
You can register it as a service:
services:
form_serializer:
class: Wooshii\SiteBundle\FormErrorsSerializer
and then use it as the author suggest:
$errors = $this->get('form_serializer')->serializeFormErrors($form, true, true);
This does the trick for me
$errors = [];
foreach ($form->getErrors(true, true) as $formError) {
$errors[] = $formError->getMessage();
}
PHP has associative arrays, meanwhile JS has 2 different data structures : object and arrays.
The JSON you want to obtain is not legal and should be :
{
"status":400,
"errorMsg":"Bad Request",
"errorReport": {
"taskfield" : "Task cannot be blank",
"taskdatefield" : "Task date needs to be within the month"
}
}
So you may want to do something like this to build your collection :
$errorCollection = array();
foreach($errors as $error){
$errorCollection[$error->getId()] = $error->getMessage();
}
(assuming the getId() method exist on $error objects)
By reading other people's answers I ended up improving it to my needs. I use it in Symfony 3.4.
To be used in a controller like this:
$formErrors = FormUtils::getErrorMessages($form);
return new JsonResponse([
'formErrors' => $formErrors,
]);
With this code in a separate Utils class
<?php
namespace MyBundle\Utils;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
class FormUtils
{
/**
* #param FormInterface $form
* #return array
*/
public static function getErrorMessages(FormInterface $form)
{
$formName = $form->getName();
$errors = [];
/** #var FormError $formError */
foreach ($form->getErrors(true, true) as $formError) {
$name = '';
$thisField = $formError->getOrigin()->getName();
$origin = $formError->getOrigin();
while ($origin = $origin->getParent()) {
if ($formName !== $origin->getName()) {
$name = $origin->getName() . '_' . $name;
}
}
$fieldName = $formName . '_' . $name . $thisField;
/**
* One field can have multiple errors
*/
if (!in_array($fieldName, $errors)) {
$errors[$fieldName] = [];
}
$errors[$fieldName][] = $formError->getMessage();
}
return $errors;
}
}
This will do the trick. This static method runs recursively through the Symfony\Component\Form\FormErrorIterator delivered by calling $form->getErrors(true, false).
<?php
namespace App\Utils;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormErrorIterator;
class FormUtils
{
public static function generateErrorsArrayFromForm(FormInterface $form)
{
$result = [];
foreach ($form->getErrors(true, false) as $formError) {
if ($formError instanceof FormError) {
$result[$formError->getOrigin()->getName()] = $formError->getMessage();
} elseif ($formError instanceof FormErrorIterator) {
$result[$formError->getForm()->getName()] = self::generateErrorsArrayFromForm($formError->getForm());
}
}
return $result;
}
}
Here is the result :
{
"houseworkSection": "All the data of the housework section must be set since the section has been requested.",
"foodSection": {
"requested": {
"requested": "This value is not valid."
}
}
}
I have a form in my project which is very simple.
It is a textbox with a label, that's it.
But when I try to get the data from those textboxes, it returns NULL.
get($id) and get($id)->getData(), both return NULL.
It's a form without any class attached to it, just to keep it simple.
The purpose of this form is to adjust a number.
What am I doing wrong? Or is there a better way to solve this?
public function makenAction()
{
$em = $this->getDoctrine()->getManager();
$orderregels = $this->getRequest()->getSession()->get('orderregelz')->getOrderregels();
$overzicht = $this->createFormBuilder();
foreach($orderregels as $value)
{
/*
* getting some values from database
*/
$overzicht->add($temp->getOrderregelD(),"text",array('label'=>$tmpcompleet));
}
$overzicht->add('Verzenden','submit');
$request = $this->getRequest();
if ($request->getMethod() == 'POST')
{
$data = array();
foreach ($orderregels as $value)
{
$data[] = $overzicht->get($value);
}
}
this is the way you get data from the request
GET
$value = $request->query->get("input_name");
POST
$value = $request->request->get("input_name");
Otherwise you can simply do:
$formData = $overzicht->getData();
and then access the values like:
$foo = $formData['foo'];
To directly access all the values of a specified form, do:
$formData = $this->getRequest()->request->get('form');
After all, you should to bind the request to form.
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
var_dump($editForm->getData()); die;
}
doc
I'm having some problems when I try upload multiples files in multiples instances of my model (tabular way).
I have a model called Files and a view that generate a form for multiple instances of that model. When I save the form without "multpart/form-data", everything works, but if I put this parameter on form and submit it, the validation shows the message that "File cannot be blank."
See my controller code bellow:
public function actionRegistration() {
$company = new Company;
$contacts = $this->getModelItens('Contact', 3);
$banks = $this->getModelItens('Bank' , 2);
$files = $this->getModelItens('File' , 2);
$company->scenario = 'create';
if($_POST['Company']) {
$company->attributes = $_POST['Company'];
$valid = $company->validate();
$valid = $this->validateModels($_POST['Contact'], $contacts) && $valid;
$valid = $this->validateModels($_POST['Bank'], $banks) && $valid;
$valid = $this->validateModels($_POST['File'], $files) && $valid;
if($valid) {
if($company->save()) {
$this->saveModels($contacts, $company->id);
$this->saveModels($banks, $company->id);
$this->saveModels($files, $company->id);
$this->redirect('/obrigado');
}
}
}
$this->render('registration', array('company' => $company, 'contacts' => $contacts, 'banks' => $banks, 'files' => $files));
}
private function getModelItens($model_name, $times, $scenario = 'create') {
$models = array();
for($i = 0; $i < $times; $i++) {
$models[$i] = new $model_name;
$models[$i]->scenario = $scenario;
}
return $models;
}
private function validateModels($forms, $models) {
$valid = true;
foreach($forms as $k => $form) {
$models[$k]->attributes = $form;
$models[$k]->position = $k;
$valid = $models[$k]->validate() && $valid;
}
return $valid;
}
private function saveModels($models, $company_id) {
foreach($models as $k => $model) {
$model->company_id = $company_id;
if($model instanceOf File) {
if($model->save()) $this->upload_file($model, "file");
} else $model->save();
}
}
private function upload_file($model, $field, $k) {
$path = Yii::app()->basePath . "/../assets/files/companies/{$model->company_id}/";
$file = CUploadedFile::getInstance($model, $field);
if($file instanceof CUploadedFile) $model->$field = $file;
if($model->$field instanceof CUploadedFile) {
if(!file_exists($path)) exec("mkdir -p {$path}");
$model->$field->saveAs($path . $model->$field);
}
}
I've tried everything but I can't fix it, any suggestion?
Thanks.
I am not sure if this assignment $models[$k]->attributes = $form; will do the $_FILES as well as the $_POST for your model. Looking at this example, I think you need to do that CUploadedFile stuff before you validate() or save() (which also validates), otherwise your file field will be blank.
I think you need this order of events:
$model->$field = CUploadedFile::getInstance($model, $field); // set file field
$model->save() OR $model->validate() // THEN validate
$model->$field->saveAs($path . $model->$field); // then save the file
Instead, you have this order of events currently:
$model->save() AND $model->validate()
$model->$field = CUploadedFile::getInstance($model, $field);
$model->$field->saveAs($path . $model->$field);
I could be way off though, I'm just looking at your code and not actually testing this. Also, I have no idea why it only throws an error when you set the multpart/form-data attribute on the form; it should not work at all without that! Maybe it was skipping validation on the file fields without that? Or attributes() was assigning something from $_POST besides the file? /shrug
UPDATE:
Another thing that might be happening, is that with "tabular" input you need to reference the input field with the '$key' as well (i.e. Model[0][imageFile]). So here, where you are calling:
$this->upload_file($model, "file");`
You probably need to call something like this, so that getInstanceByName() is getting the right field name:
$this->upload_file($model, "[".$k."]file");`
I think you will still need to re-arrange your code as I mention above too.
Good luck!
More reading:
Yii forum post about this subject
Your same question in the Yii forum
What is the corecte way to handle with al lot objects of the same type?
Example:
When i get a list of notes from the database with zend framework i get a rowset which contains an array with note data.
If the number of notes in the database is 20 records large it's no problem to create a note object for every note in the database. But if the database contains 12.500 note records what shall i do than? Try to create 12.500 objects is possible but it's shure isn't quick enough.
Ty, Mark
This is the code i use.
Code to get the data from the database:
if (is_numeric($id) && $id > 0) {
$select = $this->getDao()->select();
$select->where('methode_id = ?', $id);
$select->order('datum DESC');
$rowset = $this->getDao()->fetchAll($select);
if (null != $rowset) {
$result = $this->createObjectArray($rowset);
}
}
createObjectArray function:
protected function createObjectArray(Zend_Db_Table_Rowset_Abstract $rowset)
{
$result = array();
foreach ($rowset as $row) {
$model = new Notes();
$this->populate($row, $model);
if (isset($row['id'])) {
$result[$row['id']] = $model;
} else {
$result[] = $model;
}
}//endforeach;
return $result;
}
Populate function
private function populate($row, $model)
{
// zet de purifier uit om overhead te voorkomen
if (isset($row['id'])) {
$model->setId($row['id']);
}
if (isset($row['type'])) {
$model->setType($row['type']);
}
if (isset($row['tekst'])) {
$model->setLog($row['tekst']);
}
if (isset($row['methode_id'])) {
$model->setSurveyMethodId($row['methode_id']);
}
if (isset($row['klant_id'])) {
$model->setCustomerId($row['klant_id']);
}
if (isset($row['gebruiker_aangemaakt_tekst'])) {
$model->setCreatedByUser($row['gebruiker_aangemaakt_tekst']);
}
if (isset($row['gebruiker_gewijzigd_tekst'])) {
$model->setUpdatedByUser($row['gebruiker_gewijzigd_tekst']);
}
if (isset($row['gebruiker_aangemaakt'])) {
$model->setCreatedByUserId($row['gebruiker_aangemaakt']);
}
if (isset($row['gebruiker_gewijzigd'])) {
$model->setUpdatedByUserId($row['gebruiker_gewijzigd']);
}
if (isset($row['datum_aangemaakt'])) {
$model->setDateCreated($row['datum_aangemaakt']);
}
if (isset($row['datum_gewijzigd'])) {
$model->setDateUpdated($row['datum_gewijzigd']);
}
$model->clearMapper();
return $model;
}
You could page your requests, so you only get a set amount of notes back each time. Although I can't see a problem with "only 12,500" objects, unless your object creation is doing something costly, i.e more queries on the database etc.
I am not so sure about your question.
For eg :
//Create a single object
$obj = new NoteObject();
//Set the properties if it differs
$obj->setX( $row );
//Make use of the method
$obj->processMethod();
So this way you just need a single object. Lets see the code if its not to give you a right answer.
Edit :
What I thought was in
protected function createObjectArray(Zend_Db_Table_Rowset_Abstract $rowset)
{
$result = array();
//Create the model here
$model = new Notes();
foreach ($rowset as $row) {
//Yes populate the values
$this->populate($row, $model);
/*
And not like saving the object here in array
if (isset($row['id'])) {
$result[$row['id']] = $model;
} else {
$result[] = $model;
}*/
//Do some calculations and return the result in array
$result[$row['id']] = $this->doSomething();
}//endforeach;
return $result;
}
I am not sure why you are keeping this object there itself. Any reuse ? the probably paginate and do :-)