Yii not validating unique index over 2 columns - php

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'),
);
}

Related

Yii: Error when trying to use a 'uniqe' rule with a condition

I'm dealing with an Yii 1.1x website.
I have a model which have a title and a category id.
I'm trying to prevent from the user the possibility to enter the same title in the exact same category. (the same title in a different category is ok).
I tried using the following rule but I get an error when testing it (by entering a duplicate title in the same category)
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('str_title, id_category', 'required'),
array('str_title', 'unique', 'criteria'=>array(
'condition'=>'`id_category`=:idcategory',
'params'=>array(
':idcategory'=>$this->id_category
)
)),
);
}
As mentioned, when I try to enter a duplicate title within the same category I get this error message:
CDbCommand failed to execute the SQL statement: SQLSTATE[HY093]: Invalid parameter number: parameter was not defined. The SQL statement executed was: SELECT 1 FROM `posts` `t` WHERE (`id_category`=:idcategory) AND (`t`.`str_title`=:ycp0) LIMIT 1
Anybody know what is the problem?
You should not use attributes values in rules() method, since it is called before attributes are set:
Call rules() to get validators config.
Set attributes.
Run validation.
You should use existing extension (example) for validation of composite keys or write own validator for this (it is not that hard, usually inline validator takes like 5-10 lines of code).

Laravel errors is too misleading ether with debug mode, is there any ways to have more clear errors?

I have a spelling error in migrations:
Schema::create('business_category',function(Blueprint $table){
$table->integer('business_id')->unsinged();
$table->integer('category_id')->unsinged();
});
Schema::create('business_category',function(Blueprint $table){
$table->foreign('business_id')->references('id')->on('business');
$table->foreign('category_id')->references('id')->on('category');
});
and I run "php artisan migrate"
this error has been shown:
[Illuminate\Database\QueryException]
SQLSTATE[HY000]: General error: 1005 Can't create table
'brandecide.#sql-42 4_aa' (errno: 150) (SQL: alter table
business_category add constraint bus
iness_category_business_id_foreign foreign key (business_id)
references business (id))
this error caused by:
$table->integer('business_id')->**unsinged**();
and I should change this to
$table->integer('business_id')->**unsigned**();
to fix it.
How should I understand this from the error?
How should I understand this from the error?
You can't, unfortunately. I agree the error is quite misleading. The problem is that unique() is not a real method. (If so you'd get a method undefined exception)
Instead the call to unique() ends up being caught by Illuminate\Support\Fluent
public function __call($method, $parameters)
{
$this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true;
return $this;
}
And is then added to $this->attributes without doing any checking. This then results in no error but just an attribute (unsinged) that will never be used and a missing unsigned that causes the constraint to fail.
If you want you can create an issue (with type "proposal") on github. Maybe someone has a good idea how this can be prevented. (e.g. a whitelist of recognized methods)
You probably already have some entries in your business_category table. When running that migration, you're trying to add a column that references another table. As you didn't define any default value, MySQL tries to default the value to null, but that corresponds to nothing in that other table. When having a foreign key, the value in that column MUST correspond to a value in another table.
So to solve it, you could create a ->default(1), assuming that the id 1 exists in your business-table.

Column name must be either a string or an array yii

i got some stuck when accessing a yii's web application. I have configured as the same as the owner's setting, but while i tried to access, i got an error "Column must be either a string or an array". How could i solve it? Thanks in advance..
When reporting error messages, it helps to have the precise error message. The actual error message is: "Column name must be either a string or an array". With an exact string you can search the framework files to find where it is mentioned.
Looks like some method somewhere is passing an invalid column name to createInCondition method of CDbCommandBuilder.
See line 722: https://github.com/yiisoft/yii/blob/1.1.13/framework/db/schema/CDbCommandBuilder.php
Looking at a couple instances where that method is called, I would guess that you have a database table without a primary key somewhere. That is one possible explanation for the problem. Other explanations will require a lot more details on your part.
Provide the stack trace that the error page provides you with when in debug mode along with your table schema.
This happens when you don't have a primary key in your table and you try to do an update. I got this problem because I had a composite primary key in my table. I was being handled well on all operations until I wanted to update a model.
Just add an int primary key, call it 'id' to your table with auto increment. It should do the trick.
Be sure to disable schema caching (if you're using that) before you test this. The change wont take effect until your schema cache expires.
Maybe you do not have primary key in your table. If you use the method $model->save() to save or use method $model->update() ($model is CActiveRecord instance), you will get this error.
Because the method update in CActiveRecord using Primary key to update (Read more here
)
Source Code: framework/db/ar/CActiveRecord.php#1115
if($this->_pk===null)
$this->_pk=$this->getPrimaryKey();
$this->updateByPk($this->getOldPrimaryKey(),$this->getAttributes($attributes));
$this->_pk=$this->getPrimaryKey();
You can use method updateAll() instead of update() or updateByPk()
Take a look this link
http://www.yiiframework.com/forum/index.php/topic/3887-cdbexception-column-name-must-be-either-a-string-or-an-array/
It seems your table doesn't have a primary key or the primary key doesn't well restored which usually caused by corrupt back up file.
If you forgot add return value you will have error you showed. Simple example, your model with such method will return error on PK
...
public function relations()
{
}
...
Your have to add return value.
/**
* #return array
*/
public function relations()
{
return array();
}
If you are not using such methods you should delete them, or add 'default return values'. Otherwise it gives errors the same as it was primary key or other DB issues (because model read invalid data and didn't all things it should).

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

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.

Handling foreign key exceptions in PHP

What is the best way in PHP to handle foreign key exceptions on a mysql database? Is there a mysql class that can be used to simplify any code?
Ideally, what I want to do, as an example, is to try to delete a record where it is the foreign key parent to any number of child tables. The foreign key throws the exception, so then I would like to be able to look at each foreign key table and test it, giving meaningful feedback on the tables and number of records causing the exception. This would then be returned as the error so the end user can reference and delete the offending records.
The way I handle this is to set up my database wrapper class to always throw an exception when you encounter a database error. So, for instance, I might have a class called MySQL with the following functions:
public function query($query_string)
{
$this->queryId = mysql_query($query_string,$this->connectionId);
if (! $this->queryId) {
$this->_throwException($query_string);
}
return $this->queryId;
}
private function _throwException($query = null)
{
$msg = mysql_error().". Query was:\n\n".$query.
"\n\nError number: ".mysql_errno();
throw new Exception($msg,mysql_errno());
}
Any time a query fails, a regular PHP exception is thrown. Note that I would throw these from within other places too, like a connect() function or a selectDb() function, depending on whether the operation succeeded or not.
With that set up, you're good to go. Any place you expect that you might need to be handling a database error, do something like the following:
//assume $db has been set up to be an instance of the MySQL class
try {
$db->query("DELETE FROM parent WHERE id=123");
} catch (Exception $e) {
//uh-oh, maybe a foreign key restraint failed?
if ($e->getCode() == 'mysql foreign key error code') {
//yep, it failed. Do some stuff.
}
}
Edit
In response to the poster's comment below, you have some limited information available to you to help diagnose a foreign key issue. The error text created by a failed foreign key restraint and returned by mysql_error() looks something like this:
Cannot delete or update a parent row:
a foreign key constraint fails
(`dbname`.`childtable`, CONSTRAINT `FK_name_1` FOREIGN KEY
(`fieldName`) REFERENCES `parenttable` (`fieldName`));
If your foreign keys are complex enough that you can't be sure what might cause a foreign key error for a given query, then you could probably parse this error text to help figure it out. The command SHOW ENGINE INNODB STATUS returns a more detailed result for the latest foreign key error as well.
Otherwise, you're probably going to have to do some digging yourself. The following query will give you a list of foreign keys on a given table, which you can examine for information:
select * from information_schema.table_constraints
WHERE table_schema=schema() AND table_name='table_name';
Unfortunately, I don't think there's a magic bullet to your solution other than examining the errors and constraints very carefully.
I think the best bet would be for you to do a transaction. That way, the insert will always be valid, or not done at all. That can return an error message that you can work with as well. This will prevent you from having to manually check every table - the db does it for you.

Categories