How to create an error page to a QueryException? - php

I'm working on a commercial application and I've created Blade files for several kinds of HTTP errors. They are placed in /resources/views/errors and are working fine for authorization (503.blade.php), page not found (404.blade.php), and so on.
I've created files for 400, 403, 404, 500 and 503, until now.
The issue is when a QueryException is thrown. In this case, "Whoops, looks like something went wrong." appears.
For example, considering that name cannot be null, when I do something like this, Laravel throws a QueryException:
User::create([
'name' => null,
'email' => 'some#email.com'
]);
The exception would be:
QueryException in Connection.php line 651: SQLSTATE[23000]: Integrity
constraint violation: 1048 Column 'name' cannot be null (SQL: insert
into users (nome, email, updated_at, created_at) values (,
some#email.com, 2018-02-09 12:10:50, 2018-02-09 12:10:50))
I don't want "Whoops, looks like something went wrong." to appear to the end user, I want to show a custom page. What kind of error file do I need to create to achieve this behavior?

Try this in your controller:
try {
User::create([
'name' => null,
'email' => 'some#email.com'
]);
} catch ( \Illuminate\Database\QueryException $e) {
// show custom view
//Or
dump($e->errorInfo);
}
To catch all Query Exceptions:
You need to customize the render() method of App\Exceptions\Handler, as stated in the Docs.
public function render($request, Exception $e)
{
if ( $e instanceof \Illuminate\Database\QueryException ) {
// show custom view
//Or
dump($e->errorInfo);
}
return parent::render($request, $exception);
}

Related

Cannot POST with Postman - Error 405 / Error 500 in response - Laravel json API

I am making a JSON API with Laravel. and I'm trying to test it with POSTMAN before continuing to build a front end for it.
My API is a News Dashboard backend that does the CRUD functions.
Here are the routes:
Route::get('/news', 'App\Http\Controllers\NewsController#index')->name('news.index');
Route::get('/news/{newsItem}', 'App\Http\Controllers\NewsController#show')->name('news.show');
Route::post('/news', 'App\Http\Controllers\NewsController#store')->name('news.store');
Route::patch('/news/{newsItem}', 'App\Http\Controllers\NewsController#update')->name('news.update');
Route::delete('/news/{newsItem}', 'App\Http\Controllers\NewsController#destroy')->name('news.destroy');
The /news showing me and empty array [] , because the DB is empty for now. the postman GET is returning 200 OK too. I tried to post JSON payload using postman :
{
"title": "Example News Item",
"content": "This is the content of the news item.",
"category_id": 1,
"tags": [1, 2, 3]
}
At first I get the error 419 unknown status ; then I moved my routes from web.php to api.php and the index is /api/news instead of /news.
So I'm testing /api/news
GET http://127.0.0.1:8000/api/news/ returns 200 OK
but
POST http://127.0.0.1:8000/api/news/ returns 405 Method Not Allowed
why is this? is there something wrong with my code?
Here is the store() method in the newscontroller:
public function store(Request $request)
{
$request->validate([
'title' => 'required',
'body' => 'required',
'category_id' => 'nullable|exists:categories,id',
'tags' => 'array',
'tags.*' => 'exists:tags,id'
]);
$newsItem = new NewsItem;
$newsItem->title = $request->title;
$newsItem->body = $request->body;
$newsItem->category_id = $request->category_id;
$newsItem->url = $request->url;
$newsItem->save();
$newsItem->tags()->attach($request->tags);
return response()->json($newsItem, 201);
}
If I remove the validation I get the error 500 instead with the body:
500Internal Server Error Integrity constraint violation: 1048 Column 'title' cannot be null (SQL: insert into `news_items` (`title`, `body`, `category_id`, `url`, `updated_at`, `created_at`) values (?, ?, ?, ?, 2022-12-31 14:52:26, 2022-12-31 14:52:26))
But the thing is that my payload contains the required fields.
I don't know where the problem is originating, somehow the payload is not going to the database.
I can post new items directly from database and the App\Model is working fine too because I can post with laravel tinker and save it. But I cannot post using the postman.
Nothing was wrong with the code. The root cause lies in Postman.
You need to set Accept: application/json header in Postman.
(The hint was that the application Handler was returning HTML instead of JSON in Postman)

Expected errors in Laravel Dusk

I am looking for the Laravel Dusk equivalent of
this->expectException(InvalidArgumentException::class);
I'm new to using Laravel Dusk (running Laravel 5.7) and cannot find a way to test for an expected error. When I run the following test.
I get the following:
There was 1 error:
1)
Tests\Browser\RegistrationTest::test_user_cannot_register_with_duplicate_email
Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity
constraint violation: 19 UNIQUE constraint failed: users.email (SQL:
insert into "users" ("name", "email", "password", "updated_at",
"created_a t") values (Eoj, joe#example.com, 654321, 2018-10-16
20:35:09, 2018-10-16 20:35:09))
Caused by PDOException: SQLSTATE[23000]: Integrity constraint
violation: 19 UNIQUE constraint failed: users.email
ERRORS! Tests: 4, Assertions: 5, Errors: 1.
public function test_user_cannot_register_with_duplicate_email()
{
User::create([
'name' => 'Eoj',
'email' => 'joe#example.com',
'password' => '654321'
]);
$this->browse(function ($browser) {
$browser->visit('/') //Go to the homepage
->clickLink('Register') //Click the Register link
->assertSee('Register') //Make sure the phrase in the argument is on the page
//Fill the form with these values
->value('#name', 'Joe')
->value('#email', 'joe#example.com')
->value('#password', '123456')
->value('#password-confirm', '123456')
->click('button[type="submit"]') //Click the submit button on the page
->assertPathIs('/register') //Make sure you are still on the register page
//Make sure you see the phrase in the argument
->assertSee("The email has already been taken");
});
}
Obviously I was expecting an error - but can't work out how to tell dusk this.
When I change my code to the following I get a different error:
1) >Tests\Browser\RegistrationTest::test_user_cannot_register_with_duplicate_email
Did not see expected text [The email has already been taken] within element [body].
Failed asserting that false is true.
I am not running any other tests at the moment.
<?php
namespace Tests\Browser;
use App\User;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\RefreshDatabase;
class RegistrationTest extends DuskTestCase
{
use RefreshDatabase;
protected function setUp()
{
parent::setUp();
foreach (static::$browsers as $browser) {
$browser->driver->manage()->deleteAllCookies();
}
}
public function test_user_cannot_register_with_duplicate_email()
{
User::create([
'name' => 'Eoj',
'email' => 'someone#example.com',
'password' => '654321'
]);
$this->browse(function ($browser) {
$browser->visit('/') //Go to the homepage
->clickLink('Register') //Click the Register link
->assertSee('Register') //Make sure the phrase in the argument is on the page
//Fill the form with these values
->value('#name', 'Joe')
->value('#email', 'someone#example.com')
->value('#password', '123456')
->value('#password-confirm', '123456')
->click('button[type="submit"]') //Click the submit button on the page
->assertPathIs('/register') //Make sure you are still on the register page
//Make sure you see the phrase in the argument
->assertSee("The email has already been taken");
});
}
}
As you can see in the error:
Tests\Browser\RegistrationTest::test_user_cannot_register_with_duplicate_email Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation: 19 UNIQUE constraint failed: users.email (SQL: insert into "users" ("name", "email", "password", "updated_at", "created_a t") values (Eoj, joe#example.com, 654321, 2018-10-16 20:35:09, 2018-10-16 20:35:09))
The values are exactly the ones that you are trying to insert here:
User::create([
'name' => 'Eoj',
'email' => 'joe#example.com',
'password' => '654321'
]);
So, you can change this e-mail to a different one:
User::create([
'name' => 'Eoj',
'email' => 'someone#example.com',
'password' => '654321'
]);
And again here to the test makes sense:
->value('#email', 'someone#example.com')
This way, the email will not be duplicated, because at some point you already have a "joe#example.com".
A good practice is to reset the database before the tests.
In tests/TestCase.php you may have something like this:
use Illuminate\Foundation\Testing\RefreshDatabase; //<--HERE
abstract class TestCase extends BaseTestCase
{
use RefreshDatabase; //<--HERE
And your test must extend this class:
use Tests\TestCase; //<--HERE
class MyTest extends TestCase { //<--HERE
That way the data you be reseted, avoiding the duplicate e-mail issue in the subsequent tests.
I think the problem is that you not waiting answer from server (you are submitting form via js, right?), and assertion fires too early. Just use waitFor or waitForText in these type of scenarios.

Authenticate user on login - Laravel

I am trying to authenticate use on login using this
//Attempt to auth the user
if(! auth()->attempt(request(['email','password' => Hash::make(request('password'))])))
{
//If not redirect back
return back()->withErrors([
'message' => 'Please check your email or password'
]);
}
But I am getting this error..
QueryException in Connection.php line 647:
SQLSTATE[42S22]: Column not found: 1054 Unknown column '$2y$10$5ysCvqUiloxmtRo2comd9uaiaNkLJ0eiW6x5pDFGWESAbXr5jm5N6' in 'where clause' (SQL: select * from users where email is null and $2y$10$5ysCvqUiloxmtRo2comd9uaiaNkLJ0eiW6x5pDFGWESAbXr5jm5N6 is null limit 1)
Can please somebody help me this.
Thank you :)
This error happens because you are misusing the request() helper method.
If you do like you did:
request(['email','password' => Hash::make(request('password'))])
It will produce this array:
['email' => 'myemail#lol.com', 's9audh87h2ueahjdbas' => null]
The request() method expects an array with its values representing the name of the inputs provided in the request. So if you give it an associative array, it will ignore completely the keys and it will only grab its values.
So, to get the input from the request and use it in the authorization attempt, you have to do like this:
$userInput = request('email', 'password');
if (! auth()->attempt($userInput))
{
//do something.
}
This is true assuming that your user table has an email and password columns.
Try this:
//Attempt to auth the user
if(! auth()->attempt([
'email' => request()->get('email'),
'password' => request()->get('password')
])
{
//If not redirect back
return back()->withErrors([
'message' => 'Please check your email or password'
]);
}
a) You don't need to hash the password, Laravel does that already
b) I'm not sure what you were passing as parameters. Some type of mixed array

Catching a query exception?

I have a slug column in my database, it's unique.
If I try and store another row with a non unique slug I get a QueryException error.
I catch the error, and hope to return an error message something along the lines that "slug exists".
try {
User::create($data);
} catch (\Illuminate\Database\QueryException $e) {
//return the error
}
The above is fine, but I'm just wondering, what if another QueryException is thrown, not to do with the duplicate slug and i return a duplicate slug error message incorrectly.
Is there a way to find out what the query exception was and return an error message based on this? i know the exception provides its own message but I was hoping for something a little more user friendly.
To check if you got a QueryException because of a duplicate, check if $e->getCode() equals to 23000.
When a duplicate occurs due to a constraint, MySQL will issue signal SQLSTATE 23000 which you can use to determine what exactly went wrong. You can implement your own signal in MySQL to signal about various errors (via triggers etc)
Example:
try {
User::create($data);
} catch (\Illuminate\Database\QueryException $e) {
if($e->getCode() === '23000') {
// you got the duplicate
}
}
You can check errorInfo of the exception object
It looks like this..
+errorInfo: array:3 [▼
0 => "23000"
1 => 1062
2 => "Duplicate entry 'abc#gmail.com' for key 'users_email_unique'"
You can write following code in catch..
try{}
catch(QueryException $qe){
If ($qe->errorInfo[0] == "23000" && $qe->errorInfo[1] == "1062"){
return "Your message"
}else{
return "General Message"
}
}

Laravel Eloquent Catch Special Expection

I'm currently trying the following: In my db, I have a column called templateURL, which is a unique key, so it may only exist once. Then if something tries to submit a form, where this is the same as already exist an error is thrown, saying
Integrity constraint violation: 1062 Duplicate entry for key 'templateURL'.
Of course it does, that's why I made the column unique. But: What I want is not the default Laravel Error, but getting back to the form with the values still entered and a message (alert from bootstrap for example or just a div next to the input field) saying that this already exist and asking to chooose another one. So I need to catch this exception and do something custom instead. How can I achieve what I want?
You've to wrap your database operation into a try-catch block and catch the error and do something else when you get a error, maybe redirect with old input data with a error message.
The error code for duplicate entry is 1062. So if you get 1062 as error code that means this data is a duplicate entry. Here is the code look like for catching this exception.
try {
$data = Model::create(array(
'templateURL' => 'some value ',
));
} catch (Illuminate\Database\QueryException $e) {
$errorCode = $e->errorInfo[1];
if($errorCode == 1062){
// we have a duplicate entry problem
}
}
Or if you wish not to handle the exception yourself, you can take help of Laravel Validator. Like following in your controller
// validation rules
$rules = array(
'templateURL' => 'unique:YourTableNameHere'
);
$validator = Validator::make(Input::all(), $rules);
// check if the validation failed
if ($validator->fails()) {
// get the error messages from the validator
$messages = $validator->messages();
// redirect user back to the form with the errors from the validator
return Redirect::to('form')
->withErrors($validator);
} else {
// validation successful
$data = Model::create(array(
'templateURL' => 'some value ',
));
}
Then in your view template you can access the error messages via $errors variable.
Learn more about validation there https://laravel.com/docs/5.3/validation

Categories