how to allow duplicate entries in database in Zend Framework 2? - php

recently I've created a simple registration form and when i try to save the data, if i enter an existing username or email i get an error saying that the query is stopped becasue the field already exists
In some cases this is a great feature, and i could use it in the email case.
Im not sure if i have to set this in the form validators or in the config.php or in my module.
here is my save method:
public function saveUser(User $user)
{
$data = array(
'username' => $user->username,
'email' => $user->email,
'password' => $user->password,
);
$id = (int) $user->user_id;
if ($id == 0) {
$this->insert($data);
} elseif ($this->getUser($id)) {
$this->update(
$data,
array(
'user_id' => $user_id,
)
);
} else {
throw new \Exception('Form id does not exist');
}
}
and here is the short error:
Statement could not be executed
...
QLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'admin' for key 'username'
....
like i said, i could use this error for the email, because i want unique email, but im not sure how to catch this error and display it in a nicer format, maybe like a validation error.
any ideas on this issue?
thanks

If you are using the Zend_Form to create the forms you can use the Zend_Validate_Db_NoRecordExists to check if the email already exists or not.

This is error returned by your SQL server. To get rid of this you need to remove UNIQUE KEY from your field username.
As for displaying "nice error message" if user email is duplicate, I'm afraid the only choice you have is to check its existence before executing INSERT.
I once had database model which was throwing Database_UniqueKey_Exception( $field) which allowed you to do this in quite stylish fashion, but AFAIK Zend doesn't support special handling for unique key issues and you have to either parse error message (I wouldn't go there) or check it in advance.

Here's an example on how to achieve what you want. Zend Framework: Validate duplicate database entry
It uses the MySQL PDOException code: 23000 to check if a duplicate entry exception occurred and attaches the error message to the form. This way you won't need to make an extra database query in order to check for duplicate username entry.
Hope this helps.

Related

Unable to insert data in CodeIgniter 4

I am trying out CodeIgniter 4 and I have a problem inserting data.
In my model I only defined the table name, no methods, no nothing. I can display all data in my table but when it comes to inserting data something goes wrong.
My controller code looks like:
$data = [
'sum' => '23',
'type' => 'in',
'name' => 'asd'
];
$expense = new ExpensesModel();
try {
var_dump($expense->insert($data));
} catch (\ReflectionException $e) {
var_dump($e);
}
When I call this endpoint using Postman I get a 500 internal server error.
If I die('asd') before the try-catch I can see the message so this makes me think something happens during the insert method call.
How can I debug this ?
If all you defined within the model was the table name, then you never defined $allowedFields (https://codeigniter.com/user_guide/models/model.html):
This array should be updated with the field names that can be set
during save, insert, or update methods.
You should probably read through that documentation. Moreover, you would troubleshoot these issues by looking at the logs within your project's writable/logs directory, although in this case CI would not consider this a failure as it was you not defining what's allowed into the DB.

Laravel Eloquent, how to handle UNIQUE error?

I have a MySQL constraint to ensure unique on a composite key. When inserting a new record in my model Foo I get the expected error:
$foo = new Foo(['foo' => 42, 'bar => 1]);
$foo->save();
Error:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '42' for key 'Unique'...
One solution to avoid this error is to query the model before inserting:
if (!Foo::where('foo', 42)->where('bar', 1)->first()) {
$foo = new Foo(['foo' => 42, 'bar => 1]);
$foo->save();
}
Another one would be to catch the exception when preg_match(/UNIQUE/, $e->message) is true.
Is there any better solution?
EDIT
I noticed that in Illuminate\Database\Eloquent\Builder Laravel does the double query anyway which is a bit sad:
public function findOrNew($id, $columns = ['*'])
{
if (! is_null($model = $this->find($id, $columns))) {
return $model;
}
return $this->newModelInstance();
}
In the general case you should be dealing with database errors using the error code and not any regex.
In your particular case pre-querying or using a Laravel method that does that automatically for you, might be preferable if your intention is to overwrite/update existing data.
If you want to generally anticipate an error and handle it you should do something like:
try {
$foo = new Foo(['foo' => 42, 'bar' => 1]);
$foo->save();
} catch (\Exception $e) { // It's actually a QueryException but this works too
if ($e->getCode() == 23000) {
// Deal with duplicate key error
}
}
Refer to https://dev.mysql.com/doc/refman/5.5/en/error-reference.html for an exhaustive list of error codes (but ideally you'd only need to deal with a couple of specific errors and under very specific circumstances.
Lastly the SQL ON DUPLICATE KEY UPDATE might also work for you, however if you are doing this to silently ignore the new values then I suggest you do the error handling instead.
You can use the firstOrCreate method:
$foo = Foo::firstOrCreate(['foo' => 42, 'bar' => 1]);
This will check if the record exists in the database before creating it. If it exists, it will return the record.
For more information: https://laravel.com/docs/5.8/eloquent#inserting-and-updating-models
laravel provides you with the firstOrCreate functions, which first checks if that value exist in the database, then you also have updateOrCreate function to use incase you want to update some value, but most important one, if you want to handle only unique records then check the validation rules, you would do a FormRequest and add unique rule to that field, after that laravel with provide you with error messages, so you handle the error on the client side
The verification need to be done at Controller level (like laravel Validator) and it's better to do a count() instead of the first().
There are different solution depending on what you wanna do when the entity exists in the database. For example you can do updateOrCreate()
$foo = App\Foo::updateOrCreate(['foo' => 42, 'bar' => 1]);
or throw an exception

Laravel duplicate entry after force delete

Working on a Laravel 4.2 app (yes, it's an older app not worth upgrading)
Trying handle the case where a user was soft deleted and wants to create an account again.
Having the issue that registering a new user doesn't work as the 'email' column is unique and the soft deleted record still exists.
I would much rather prefer to create a new user record (with all other fields empty), with same incrementing ID to keep relations with other soft deleted records.
So physically delete the old record and create a new one with the old ID? Right? Not working.
Calling forceDelete() on the record works, it does remove it from the Database, but then trying to create a new record immediately afterward throws a 'duplicate key' exception. Refreshing the page it is no longer a problem, as it doesn't see the old record.
It seems that the error is coming from MYSQL itself, though php should be waiting for a response to the delete query before executing the insert query, so mysql should know at this point that the record has been removed.
Is it possible that this error is actually coming from some sort of memory cache in Laravel?
I am also using the Sentinel authentication package, if that is of relevance.
Here is the basic code:
$found = User::where('email', $email)->first();
if ($found->deleted_at) {
$old_id = $found->id;
$found->forceDelete();
$found->save();
$unique_id = $this->generateUnique(NULL, $first_name . $last_name);
$user = Sentinel::registerAndActivate(array(
'id' => $old_id,
'email' => $email,
'password' => $password,
'first_name' => $first_name,
'last_name' => $last_name,
'unique_id' => $unique_id,
'preferred_lang' => $this->locale,
));
}
This results in a
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'whoever#mail.com' for key 'users_email_unique'
Does anyone know why this is? Is there an easy solution?
From the docs (https://laravel.com/docs/4.2/eloquent):
To restore a soft deleted model into an active state, use the restore method:
$user->restore();
To determine if a given model instance has been soft deleted, you may use the trashed method:
if ($user->trashed())
{
//
}
I actually got my code to work...
I read somewhere that I had to call
$found->save()
to update the deleted record, but that still had not worked for me.
Oddly enough, switching to
$found->update()
worked.
You can restore deleted row like this:
$deletedRow = User::withTrashed()->where('email', $email)->first();
if($deletedRow) {
$deletedRow->restore();
}

Laravel Auth, custom database & table, {"error":{"type":"ErrorException","message":"Undefined index: password"

Creating a login route & function using Laravel's Auth class and my database. Not using Eloquent due to the large legacy database that must be used.
I know SHA1 shouldn't be used but the thousands of accounts in the database use it and I haven't migrated.
Route::post('login', function() {
$user = array(
'username_c' => Input::get('username'),
'password_c' => sha1(Input::get('password'))
);
if(Input::has('username') && Input::has('password')) {
if(Auth::attempt($user)) {
return json_encode(array('result' => true));
}
}
return json_encode(array('result' => false));
});
ONLY when I enter valid credentials, it spits back this error in the console:
{"error":{"type":"ErrorException","message":"Undefined index: password","file":"\/var\/www\/html\/vendor\/laravel\/framework\/src\/Illuminate\/Auth\/DatabaseUserProvider.php","line":135}}
What could be the problem here? I edited app/config/auth.php and changed the 'table' to my table, and the 'driver' to 'database'
From reading Laravel's docs., that's all I need to change. What's causing this?
If you look at the line that is being referenced, you see this:
$plain = $credentials['password'];
The authentication is expecting a 'password' field to exist in the credentials provided to Auth::attempt, and you're giving it 'password_c' instead, which is why you're getting that error.
The second line in that method is this:
return $this->hasher->check($plain, $user->getAuthPassword());
This is basically checking whether the plain password provided in the input matches the value that is stored in the database. This means that your input key name does not need to match the name of the database column. The name of the database column is determined by the $user->getAuthPassword() method call above. You will need to make the key that you pass to Auth::attempt to be 'password', though, instead of 'password_c'.
But this isn't just going to work, because as you've probably already noticed in the second line above that class is going to expect an actual instance of a User model, and by your own admission you don't have any Eloquent models.
My suggestion would be to go ahead and create the user eloquent model, because I believe you're going to have to. You don't have to turn all of your tables into models, but if you want authentication to work with Laravel's Auth facade then I don't think there's any way around it.
Update
This answer also has a good method for updating your current passwords to use the Laravel hash during authentication attempts with a fallback to using MD5 (the old hash function, in your case you could use sha1).
Update 2
You need to change your authentication logic to look something like this:
Route::post('login', function() {
if(Input::has('username') && Input::has('password')) {
$user = array(
'username' => Input::get('username'),
'password' => sha1(Input::get('password'))
);
if(Auth::attempt($user)) {
return json_encode(array('result' => true));
}
}
return json_encode(array('result' => false));
});
Ensure that your user model's getAuthPassword method returns the name of the password column and the getAuthIdentifier returns the name of the username column.

Yii not validating unique index over 2 columns

I have added a unique key over two columns in my database;
ALTER TABLE `foo`
ADD UNIQUE KEY `foo_uk` (`field1`, `field2`);
This is working correctly, I get an integrity constraint violation if I attempt to add a row where the values of these two fields already exist in another row.
However, I can't get Yii to pick up the restraint.
In my model I've added a rule;
public function rules()
{
return array(
...
array('field1', 'unique', 'criteria'=>array(
'condition'=>'`field2`=:field2',
'params'=>array(
':field2'=>$this->field2
)
),
...
);
}
I can see this rule is running when I save the model and is performing the following query during validation:
SELECT 1 FROM `foo` `t` WHERE (`field2`=:field2) AND (`t`.`field1`=:ycp0) LIMIT 1
I've checked by running this command in mysql and it's returning a correct result, When the table is empty, it returns 0 sets, and when I try to add an identical row, it's returning 1.
However, for some reason it's not triggering a fail on validation, instead it's throwing a CDbException:
CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity
constraint violation: 1062 Duplicate entry 'field1TestData-field2TestData' for
key 'foo_uk'. The SQL statement executed was: INSERT INTO `foo` (`field1`,
`field2`...etc) VALUES (:yp0, :yp1...etc)
I would expect the validation before the save occurs to throw an error gracefully and take me back to the form with the correct error message ('field1' and 'field2' must be unique or whatever it would be)?
Please could someone point me in the right direction as to what's going on here. Do I need to create my own validation method, I am assuming here that Yii should handle the validation error as it's in the rules method for the model?
May be this extension will help you : unique-attributes-validator
From the extension page:
Yii comes with bucket of validators including a unique validator which validates whether an attribute value is unique in the corresponding database table. But what if your database unique constraint contains more than one attribute?
This is what I'm using on my projects.
here's an example
public function rules() {
return array(
array('firstKey', 'UniqueAttributesValidator', 'with'=>'secondKey'),
);
}

Categories