I have a Laravel application, and on one of the pages, I want to allow the user to be able to update some values in the database, by entering/ changing data in a couple of textboxes.
The Angular function called by the (change) attribute of these HTML textboxes is:
updatePreferredAddresseeDetails($event, payer) {
console.log("updatePreferredAddresseeDetails() called ");
const contact = payer['contacts'][$event.currentTarget.selectedIndex];
payer.loading = true;
payer.originalAddresseeName = payer.addresseename;
payer.originalAddresseeNamePdf = payer.addresseenamepdf;
payer.ADDRESSEENAME = $event.contactPreferredName;
payer.ADDRESSEENAMEPDF = $event.contactPreferredAddresseeName;
this.provService.updatePreferredAddresseeDetails(payer).subscribe(
(response:any) => {
payer.addresseename = response.addresseename;
payer.addresseenamepdf = response.addresseenamepdf;
const message = new Message();
message.type = MessageType.SUCCESS;
message.message = 'Preferred Addressee details have been updated. ';
this.messagingService.emitMessage(message);
payer.loading = false;
},
(error:any) => {
//reset the names back to what they were originally because saving failed
payer.addresseename = payer.originalAddresseeName;
const message = new Message();
message.type = MessageType.ERROR;
message.message = error.message || 'There was a problem updaing the preferred addressee details. If the problem persists, please contact us.';
this.messagingService.emitMessage(message);
payer.loading = false;
}
);
}
The PHP function called by the above Angular function, which should be setting the values in the database is:
public function updatePreferredAddresseeDetails(Request $request)
{
try
{
DB::beginTransaction();
$transactionContactId = $request->input('transactionContactId');
$transactionItemId = $request->input('transactionItemId');
if ($transactionItem = transactionItem::find($transactionItemId))
{
$transaction = $transactionItem->transaction;
if (User::canAccessTransaction( auth()->user()->user, $transaction))
{
$account = Account::find($transaction->accountId);
$account->savePropertyValueByPropertyTag('ADDRESSEENAME', $request->input('contactPreferredName'));
$account->savePropertyValueByPropertyTag('ADDRESSEENAMEPDF', $request->input('contactPreferredAddresseeName'));
$account->save();
DB::commit();
return response()->json([
'success' => true,
'addresseeName' => $account->ADDRESSEENAME,
'addresseeNamePdf' => $account->ADDRESSEENAMEPDF,
]);
}
else
{
return response()->json([
'success' => false,
]);
}
dd("transactionItem: ", $transactionItem);
}
else
{
dd("transactionItem could not be found ");
}
}
catch(Exception $e)
{
$message = $e->getMessage();
if (empty($message))
{
$message = "Preferred addressee details could not be updated. ";
}
DB::rollback();
return response()->json([
'error' => true,
'message' => $message
], 500);
}
}
However, when I enter new values/ update an existing value in one of the textboxes, and then tab out of it, I can see in the browser console that the Angular function is called, and that it in turn calls the PHP function- but in the Network->Preview tab of the console, I see the output:
{success: true, addresseeName: null, addresseeNamePdf: null}
addresseeName: null
addresseeNamePdf: null
success: true
so for some reason, it seems that these values are not actually being updated in the database. Why is this? What am I doing wrong? How can I ensure that the database values are correctly updated from this function?
Edit
Looks like #Devon was possibly right with his comment about the function being used... I had a look the user.php file (which is where canAccessTransaction() is defined), and there was another function: userCanEditAccount(), which I think is probably the one I want. It's defined with:
private static function userCanEditAccount($userId, $accountId)
{
return Account::canUserEditAccount( $userId, $accountId );
}
so I changed that part inside the updatePreferredAddresseeDetails() function to:
if(User::userCanEditAccount( $request->userId, $request->accountId)
{
$account = Account::find($request->accountId);
$account->savePropertyValueByPropertyTag('ADDRESSEENAME', $request->input('contactPreferredName'));
$account->savePropertyValueByPropertyTag('ADDRESSEENAMEPDF', $request->input('contactPreferredAddresseeName'));
$account->save();
dd("request: ", $request->all());
DB::commit();
return response()->json([
'success' => true,
'addresseeName' => $account->ADDRESSEENAME,
'addresseeNamePdf' => $account->ADDRESSEENAMEPDF,
]);
}
else
{
return response()->json([
'success' => false,
]);
}
But when the page loads now, before I actually interact with it at all, I get an error in the console that says:
Parse error: syntax error, unexpected ';'
on the line
$account = Account::find($request->accountId);
but I'm pretty sure that ; should be there- what else could be causing this?
Related
I am new at PHP. We are creating REST API in Phalcon and I've created a put request. It already works, but I would like to check if update has really happened before sending a success response. So I've created a conditional for that ( if (!$product->update()) ), but it always returns 'true'. How can I check if any field has changed in a record?
public function put()
{
$id = $this->getParam('id');
$input = $this->getRawData();
$product = Product::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id]
]);
if ($product === null){
throw new NotFoundException();
}
$product->assign($input);
$product->update();
if (!$product->update()) {
$this->errorResponse($product->getMessages());
} else {
$this->successResponse($product->toArray($product->update()));
}
}
You can use Model Events, i.e. afterUpdate and notSaved, like:
use Phalcon\Mvc\Model;
use Phalcon\Http\Response;
class ModelBase extends Model
{
public function afterUpdate()
{
$response = new Response();
$response->setJsonContent([
'success' => true,
'message' => "Record updated"
])->send();
}
public function notSaved()
{
$response = new Response();
$response->setJsonContent([
'success' => false,
'message' => 'Record not saved'
])->send();
}
}
The Product and all other models will extend ModelBase. Then your code could be:
public function put()
{
$id = $this->getParam('id');
$input = $this->getRawData();
$product = Product::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id]
]);
if ($product === null){
throw new NotFoundException();
}
$product->assign($input);
$product->update();
}
And Phalcon event will respond if the model was updated or not. If you prefer, you can also use custom http response codes for update or notSaved. More information about Model Events in the documentation
You are calling $product->update() three times. You do it once after the assign, then again for your if test, which is why it's always returning TRUE there I believe, and once inside the toArray() which may not actually return anything since the second and third updates don't have any data to update (not sure about that though).
I would code this as follows:
$product->assign($input);
$results = $product->update();
if (!results) {
$this->errorResponse($product->getMessages());
} else {
$this->successResponse($results->toArray());
}
I am assuming that the $product->assign($input); statement is working as expected to update the $product data for you. I don't use that. I prefer to do direct assignments for updates so nothing is left to chance, ie. $product->whatever = $input['whatever'];.
Give this a try and hopefully it will work as expected for you.
I'm using Laravel 8 and Sweet Alert and I wanted to show the error messages that were caught by error Exception as Sweet Alert error popup message.
Basically here is my Controller method:
try{
...
}catch(\Exception $e){
// Alert::error('Error Title', 'Error Message');
return json_encode(['status' => '500', 'msg' => __('message.error-server')]);
}
So as you can see I have json encoded an associative array that holds the information of the error message but I don't want to return it. In fact I have to show it as Alert::error(...).
So how can I do that?
UPDATE 1:
I just tested this but not showing me the error as Alert:
public function destroy(User $user)
{
try{
$useradasd->is_active = 0;
$useradasd->is_deleted = 1;
$useradasd->remover_id = Auth::id();
$useradasd->save();
}catch(\Exception $e){
$attributes = ['status' => '500', 'msg' => __('message.error-server')];
$dataAttributes = array_map(function($value, $key) {
return $key.'=>'.$value;
}, array_values($attributes), array_keys($attributes));
$associativeString = implode(', ', $dataAttributes);
Alert::error($associativeString);
}
Alert::success('Removed', 'That user is deleted');
return back();
}
UPDATE 2:
I just tried this, but does not catch the error exception and show me the Alert::success(...) instead.
public function destroy(User $user)
{
try{
$useradasd->is_active = 0;
$useradasd->is_deleted = 1;
$useradasd->remover_id = Auth::id();
$useradasd->save();
}catch(\Exception $e){
$attributes = ['status' => '500', 'msg' => __('message.error-server')];
$dataAttributes = array_map(function($value, $key) {
return $key.'=>'.$value;
}, array_values($attributes), array_keys($attributes));
$associativeString = implode(', ', $dataAttributes);
Alert::error('Error',$associativeString);
}
Alert::success('Removed', 'That user is deleted');
return back();
}
UPDATE #3:
I can finally get the error:
But I wanted to show $attributes['status'] which is 500 as Error Title and the body of that error contains $attributes['msg']. How can I do that?
You can do this:
$attributes = ['status' => '500', 'msg' => __('message.error-server')
$dataAttributes = array_map(function($value, $key) {
return $key.'=>'.$value;
}, array_values($attributes), array_keys($attributes));
$associativeString = implode(', ', $dataAttributes);
What this does basically is, it will convert the associative array to string first and then you can use the final string i.e.$associativeString in your alert as:
Alert::error($associativeString);
this will output like:
status => 500, msg => Internal Server Error
you can modify return $key.'=>'.$value; inside map to shape the final output the way you want.
UPDATE #1
Looking at the SweetAlert docs you used, I believe it follows the syntax of Alert::[type]([Title],[message]), You can update the alert from this:
Alert::error($associativeString);
to this:
Alert::error('Error',$associativeString);
UPDATE #2
public function destroy(User $user)
{
try{
$useradasd->is_active = 0;
$useradasd->is_deleted = 1;
$useradasd->remover_id = Auth::id();
$useradasd->save();
throw new Exception("Custom Exception from try block");
}catch(\Exception $e){
$attributes = ['status' => '500', 'msg' => $e->getMessage()];
$dataAttributes = array_map(function($value, $key) {
return $key.'=>'.$value;
}, array_values($attributes), array_keys($attributes));
$associativeString = implode(', ', $dataAttributes);
Alert::error('Error',$associativeString);
}
return back();
}
UPDATE #3
You should work on improving your concepts of arrays in php and how they work learn How PHP associative arrays work, now according to your requirement you should not convert the associative array to string as I suggested in my original answer. try this instead:
public function destroy(User $user)
{
try{
$useradasd->is_active = 0;
$useradasd->is_deleted = 1;
$useradasd->remover_id = Auth::id();
$useradasd->save();
throw new Exception("Custom Exception from try block");
}catch(\Exception $e){
$attributes = ['status' => '500', 'msg' => $e->getMessage()];
Alert::error($attributes['status'],$attributes['msg']);
}
return back();
}
the Alert::error() takes two parameters the first one is Title and the second is the message, you just have to fetch the value from related keys of associative array i.e. status and msg in our case.
This Should Work
Alert::error(json_encode(['status' => '500', 'msg' => __('message.error-server')],JSON_PRETTY_PRINT));
I want to ask how to to check the data if we input data if there is the same data that cannot be inserted.
My code:
$obj = new Pengajuan();
// $obj->id_pengajuan = $req->input('kode');
$obj->id_nasabah = $req->input('id_nasabah');
$obj->tgl_pengajuan = $req->input('tgl_pengajuan');
$obj->besar_pinjaman = $req->input('besar_pinjaman');
$obj->status = $req->input('status');
$simpan = $obj->save();
if ($simpan == 1) {
$status = "Tersmpan";
} else {
$status = "Gagal";
}
echo json_encode(array("status" => $status));
Above of the code add a validation like below:
$this->validate([
'id_nasabah' =>'unique:pengajuans'
]) ;
And then rest of your controller code.
try this:
public function store(Request $request)
{
$this->validate($request,[
'id_nasabah'=>'required|exists:your_table_name,id',
'tgl_pengajuan'=>'more_validations',
'besar_pinjaman'=>'more_validations',
]);
//if $this->validate() fails, it will return a response 422 code error
//else it will continue with the creation of your object
//is a good idea to use a try-catch in case of something goes wrong
try
{
$pengajuan=Pengajuan::create($request->only('id_nasabah','tgl_pengajuan','besar_pinjaman'));
return response->json([
'pengajuan'=>$pengajuan,
'status'=>'Tersmpan',
],200);//return a http code 200 ok
}
catch(Exception $e)
{
//'message'=>'this will return what is the error and line'
return response()->json([
'message'=>$e->getMessage().'/'.$e->getLine(),
'status'=>'Gagal',
],422);
}
}
I'm completely lost as to why this is happening, and it happens about 50% of the time.
I have a check to see if a user exists by email and last name, and if they do, run some code. If the user doesn't exist, then create the user, and then run some code.
I've done various testing with dummy data, and even if a user doesn't exist, it first creates them, but then runs the code in the "if" block.
Here's what I have.
if (User::existsByEmailAndLastName($params->email, $params->lastName)) {
var_dump('user already exists');
} else {
User::createNew($params);
var_dump("Creating a new user...");
}
And here are the respective methods:
public static function existsByEmailAndLastName($email, $lastName) {
return User::find()->where([
'email' => $email,
])->andWhere([
'last_name' => $lastName
])->one();
}
public static function createNew($params) {
$user = new User;
$user->first_name = $params->firstName;
$user->last_name = $params->lastName;
$user->email = $params->email;
$user->address = $params->address;
$user->address_2 = $params->address_2;
$user->city = $params->city;
$user->province = $params->province;
$user->country = $params->country;
$user->phone = $params->phone;
$user->postal_code = $params->postal_code;
return $user->insert();
}
I've tried flushing the cache. I've tried it with raw SQL queries using Yii::$app->db->createCommand(), but nothing seems to be working. I'm totally stumped.
Does anyone know why it would first create the user, and then do the check in the if statement?
Editing with controller code:
public function actionComplete()
{
if (Yii::$app->basket->isEmpty()) {
return $this->redirect('basket', 302);
}
$guest = Yii::$app->request->get('guest');
$params = new CompletePaymentForm;
$post = Yii::$app->request->post();
if ($this->userInfo || $guest) {
if ($params->load($post) && $params->validate()) {
if (!User::isEmailValid($params->email)) {
throw new UserException('Please provide a valid email.');
}
if (!User::existsByEmailAndLastName($params->email, $params->lastName)) {
User::createNew($params);
echo "creating new user";
} else {
echo "user already exists";
}
}
return $this->render('complete', [
'model' => $completeDonationForm
]);
}
return $this->render('complete-login-or-guest');
}
Here's the answer after multiple tries:
Passing an 'ajaxParam' parameters with the ActiveForm widget to define the name of the GET parameter that will be sent if the request is an ajax request. I named my parameter "ajax".
Here's what the beginning of the ActiveForm looks like:
$form = ActiveForm::begin([
'id' => 'complete-form',
'ajaxParam' => 'ajax'
])
And then I added this check in my controller:
if (Yii::$app->request->get('ajax') || Yii::$app->request->isAjax) {
return false;
}
It was an ajax issue, so thanks a bunch to Yupik for pointing me towards it (accepting his answer since it lead me here).
You can put validation like below in your model:
public function rules() { return [ [['email'], 'functionName'], [['lastname'], 'functionforlastName'], ];}
public function functionName($attribute, $params) {
$usercheck=User::find()->where(['email' => $email])->one();
if($usercheck)
{
$this->addError($attribute, 'Email already exists!');
}
}
and create/apply same function for lastname.
put in form fields email and lastname => ['enableAjaxValidation' => true]
In Create function in controller
use yii\web\Response;
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
else if ($model->load(Yii::$app->request->post()))
{
//place your code here
}
Add 'enableAjaxValidation' => false to your ActiveForm params in view. It happens because yii sends request to your action to validate this model, but it's not handled before your if statement.
How to test specific validation errors in php unit thrown in validation error ?
with below code we could check session has errors, but not the exact error
$this->assertSessionHasErrors();
assertSessionHasErrors can receive an array, as documented:
$this->assertSessionHasErrors([
'field' => 'Field error message.'
]);
Got the answer
$errors = session('errors');
$this->assertSessionHasErrors();
$this->assertEquals($errors->get('name')[0],"Your error message for validation");
$errors is MessageBag object which stored in laravel session when validation error thrown
using $errors->get('name') you could see all the validation errors as an array
You may use the combination of assertStatus and assertJson
...
->assertStatus(422)
->assertJson([
'errors' => [
'field' => [
'Error message'
]
]
]);
You can use $response->assertSessionHasErrors('key')
https://laravel.com/docs/7.x/http-tests#assert-session-has-errors
an example for required attribute will be
$response = $this->json('POST', '/api/courses', $this->data([
'name' => '',
'api_token' => $this->user->api_token
]));
$response->assertSessionHasErrors('name');
You can add an extra assertion, to make sure that no entry was added to the database, in this case "assert no course was added"
$this->assertCount(0, Course::all());
For multiple required attributes you may use a loop something like the following:
collect(['name', 'description', 'amount'])->each(function ($field) {
$response = $this->json('POST', '/api/courses', $this->data([
$field => '',
'api_token' => $this->user->api_token
]));
$response->assertSessionHasErrors($field);
$this->assertCount(0, Course::all());
});
First I use
$this->post()
instead of
$this->jsonPost()
Dont know why, for certain reason, the session would not come out.
Then I just use
$response->assertSessionHasErrors('field_name', 'Error Message!');
To find out what are the error message, you must dump it
$response->dumpSession();
There is also a more elegant way in my opinion:
If you throw an exception via the class GeneralException you can check in a unit test if the session has a flash_danger from throwing a exception.
Lets do a practical example: We want to test that the admin cannot activate an already activated catalogue item.
Test function
public function an_admin_cannot_activate_an_activated_catalogue()
{
$catalogue = factory(Catalogue::class)->states('active')->create();
$response = $this->get("/admin/questionnaire/catalogue/{$catalogue->id}/activate");
$response->assertSessionHas(['flash_danger' => __('The catalogue item is already activated.')]);
}
Model/Repro function
If it is activated we throw an Exception which then can be checked by the test function.
public function activate(Catalogue $catalogue) : Catalogue
{
if ($catalogue->is_active) {
throw new GeneralException(__('The catalogue item is already activated.'));
}
$catalogue->is_active = 1;
$activated = $catalogue->save();
if($activated) {
return $catalogue;
}
}
actually you can easily throw errors from validation using dd() and session('errors')
since errors bag is stored in session you could add dd(session('errors')) in your unit tests to see which fields you are missing.
and finally you can write more proper test by adding $response->assertSessionHasErrors('field_name');
Laravel 7;
In my case, I needed to ensure there was no error.
But below did ignore form-validation errors (at least mine).
$response->assertSessionHasNoErrors();
Hence I created a custom assert function in base TestCase class, like:
use PHPUnit\Framework\Constraint\RegularExpression;
// ...
public static function assertNoErrorReport(TestResponse $response)
{
$error = static::getViewError($response);
if ( ! empty($error)) {
$this->fail('View contains error:' . PHP_EOL . $error);
}
$response->assertSessionHasNoErrors();
}
public function assertHasErrorRegExp(string $pattern, TestResponse $response, string $message = '')
{
$error = static::getViewError($response);
static::assertThat($error, new RegularExpression($pattern),
empty($message) ? $error : $message);
}
public static function getViewError(TestResponse $response)
{
$content = $response->getOriginalContent();
if ( ! $content) {
static::fail('View content missing.');
}
if ($content instanceof View) {
$data = $content->gatherData();
$error = $data['error'] ?? $data['errors'] ?? null;
// Casts array to string.
if (is_array($error)) {
$error = '[' . join(', ', $error) . ']';
}
// Casts Error-bag to string.
$error = '' . $error;
if ($error === '[]') {
return null;
}
} else {
static::fail('Response is not a View.');
}
return $data;
}
However, my assertHasErrorRegExp(...) could be used for OP's case.