Yii2: Prevent empty string in attribute of ActiverRecord - mysql

What's the best way to prevent that an empty string gets inserted into a field of an MySQL's InnoDB table? Allowed values are strings with more than zero characters or NULL.
I'm asking that because ActiveRecord model objects often get loaded with view's form data which don't know and thus don't send NULL values. In such a case I'd prefer that a NULL gets stored instead of the empty string.
Should I define a rule? Should I implement a setter? Use a trigger?

You should simply use the default validator, add this rule to your model :
public function rules()
{
return [
// ...
['attribute', 'default', 'value' => null],
// ...
];
}

Related

Date condition (visits)

I'm trying to make a condition where if the registry has the same the same image as the same date cannot be saved
public function validateVisit()
{
if (Visit::find()->where(['date_visit'=>$this->date_visit])->all()) {
if (Visit::find()->where(['imagen_id'=>$this->imagen_id])->all()) {
$this->addError('imagen_id', 'Already exists this visit within the range.');
}
}
}
In my code it does not save when the image that is entered already exists in the database, in my case, I require that you do not save when the same date that is entered with the same image already exists
You can use unique validator for this.
public function rules()
{
return [
[['imagen_id'], 'unique', 'targetAttribute' => ['imagen_id', 'date_visit']],
// ... other validations ...
];
}
The first item in rule definition ['date_visit'] says to what attribute the error will be set. The targetAttribute define combination of attributes that must be unique. In this case the validation will only pass when the combination of imagen_id and date_visit attributes doesn't exist.
See more about unique validator.

Symfony 2 / Doctrine Not Saving Any Zeros in varchar field

UPDATE
As asked for,
$NewUser = new Users();
$Form = $this->createForm(new UserType(), $NewUser, [])
->add('save', 'submit', ['label' => 'Save',
'attr' => ['class' => 'SaveButton ftLeft'],
]);
$Form->handleRequest($request);
if ($Form->isValid()) {
/*
Sometimes add more data to the entity.....
*/
$em = $this->getDoctrine()->getManager();
$em->persist( $NewUser );
$em->flush();
}
$FormRender = $Form->createView();
return $this->render('xxxxBundle:Users/add.html.twig',
['AddUser' => $FormRender]);
Sometimes I will add extra to the entity, on fields being set within the php code, e.g. account sign up date, but in most cases just save the entity with the form data.
Very simple issue, I have a new (ish) system and have notice that when trying to save zeros, for anything, phone number inputs, it does not save any zeros?
I am using YMAL files to control my doctrine ORM, here is how I have set it up within my User table,
mobileNumber:
type: string
nullable: false
length: 50
options:
fixed: false
column: mobile_number
This does save/make the field has a varchar, and thought nothing about it until I did a random test and saw it was not saving any leading zeros?
I know you can not save leading zeros in a int field type, but this is a varchar. Also when I go into the database, and manually add a zero to that input, its fine, so I take its not the database. Something to do with how I get doctrine to save the data?
If so how do I change that? I have just been saving the data with a standard persist() and flush() commands? Not sure why it would not save the zeros? I was thinking that I had to change the YMAL type to something like phone? I have been over the docs, can not see it.
All help most welcome..
Thanks.
The reason is in your comment:
User form type has the mobile number set to 'number' input.
This value is being casted to int by the Form component. Change the field type to regular string.
If you want to validate value to be only digits, use Validation component

Ignore validation on soft deleted models

I have user table. I created a form with 3 fields:
Username
phonenumber
status
The first two fields are unique. Model rules for those fields look like this:
[['Username', 'phonenumber'], 'required'],
[['Username', 'phonenumber'], 'unique'],
I use soft deletion, so when record is deleted, it actually stays in database but status value will change to 0.
The problem is, if I add a record with existing username it shows an error message like "already added". I need to ignore validation if username have a status with value 0.
Use filter property of UniqueValidator
public function rules()
{
return [
...
['username', 'unique', 'filter' => ['<>', 'status', 0]];
...
];
}
It's better to declare constant instead of 0 (something like const STATUS_DELETED = 0) and user it as self::STATUS_DELETED inside of User class. Also you can use != instead of <>.
The last recommendation will be to use username instead of Username to follow convention of naming database table columns.
Read more about ways of declaring filter in official docs.
The ways of setting filter condition as array is described here.
You can use your own function to decide the given username already exists in active status or not. Use this function in "when" property of your unique validation rule.
Have a look :
public function rules()
{
$check = function($model) {
$existActiveUser = User::model()->findByAttributes(array("username"=>$model->username,"status"=>1));
if($existActiveUser)
return true;
else
return false;
};
return [
['Username', 'phonenumber'], 'required'],
[['Username','phonenumber'],'unique','when'=>$check],
}

Preventing malicious users update data at add action

Here is a basic add action:
public function add()
{
$article = $this->Articles->newEntity();
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->data);
if ($this->Articles->save($article)) {
$this->Flash->success('Success.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('Fail.');
}
}
$this->set(compact('article'));
}
If a malicious user injects at form a field with name id and set the value of this field to 2. Since the user do that the id value will be in $this->request->data so at $this->Articles->patchEntity($article, $this->request->data) this id will be patched and at $this->Articles->save($article) the record 2 will be updated instead of create a new record??
Depends.
Entity::$_accessible
If you baked your models, then this shouldn't happen, as the primary key field will not be included in the entities _accessible property, which defines the fields that can be mass assigned when creating/patching entities. (this behavior changed lately)
If you baked your models, then this shouldn't happen, as the primary key field(s) will be set to be non-assignable in the entities _accessible property, which means that these the fields cannot be set via mass assignment when creating/patching entities.
If you didn't baked your models and haven't defined the _accessible property, or added the primary key field to it, then yes, in case the posted data makes it to the patching mechanism, then that is what will happen, you'll be left with an UPDATE instead of an INSERT.
The Security component
The Security component will prevent form tampering, and reject requests with modified forms. If you'd use it, then the form data wouldn't make it to the add() method in the first place.
There's also the fieldList option
The fieldList option can be used when creating/patching entities in order to specifiy the fields that are allowed to be set on the entity. Sparse out the id field, and it cannot be injected anymore.
$article = $this->Articles->patchEntity($article, $this->request->data, [
'fieldList' => [
'title',
'body',
//...
]
]);
And finally, validation
Validation can prevent injections too, however that might be considered a little wonky. A custom rule that simply returns false would for example do it, you could create an additional validator, something like
public function validationAdd(Validator $validator) {
return
$this->validationDefault($validator)
->add('id', 'mustNotBePresent', ['rule' => function() {
return false;
}]);
}
which could then be used when patching the entity like
$article = $this->Articles->patchEntity($article, $this->request->data, [
'validate' => 'add'
]);

How to do Kohana Validation of $_serialize_column inside ORM

The validation on Kohana ORM is done using rules
function rules()
{
return array(
'username' => array(
array('not_empty'),
array(array($this, 'availability')),
)
);
}
I'm struggling to validate a JSON encoded column using $_serialize_columns.
class Model_Admin extends ORM {
protected $_belongs_to = array();
protected $_has_many = array(
'plans' => array(),
'groups' => array(),
'transactions' => array(),
'logins' => array()
);
protected $_serialize_columns = array('data');
/**
* #param array $data
* #param Validation $validation
*
* #return bool
*/
public function data($data, $validation)
{
return
Validation::factory(json_decode($data, TRUE))
// ... rules ...
->check();
}
public function rules()
{
return array(
'data' => array(
array(array($this, 'data'), array(':value',':validation')
)
);
}
}
the array that gets encoded is:
array(
'name' => '',
'address' => '',
'phone' => '',
'postalcode' => ''
);
the data method receives the json encoded data, because the ORM runs the filters before doing the validation, so I need to convert it back to an associative array, then create a new validation object to check specifically for the content of that array. Because I can't merge Validation rules from another Validation instance
Updated Answer
The use of a second validation object is necessary since save() causes the internal model validation object to be checked. This means that rules added to the validation object being checked from a validation rule will be ignored (Validation->check() imports the rules into local scope before looping).
Since the data itself is technically another object (in the sense of object relationships, it has its own dataset that needs validation) the ideal solution would be to find a way to create a real model that saves the data.
There are numerous other benefits to saving data with proper database column definitions, not least if you need to perform data property lookups, make in-situ changes etc. (which would otherwise require unserializing the data column, potetnailly in all rows).
There are some alternatives, but they feel like kludges to me:
Create a model that represents the data object and add rules to it, using check() to validate the data (problem: will require a lot of maintenance, no real-world table means columns must be manually defined).
Set the data as real columns in the Admin model, and use a filter that will convert it into the data column on set (problem: again, must manually define the columns and exclude the additional columns from the save operation).
I hope this is of some use.
Original Answer
The Kohana ORM save() method permits the inclusion of an "extra" validation object, which is merged into the main ORM validation object namespace.
This is documented briefly here.
If I have understood correctly, I think you are looking to do something like this:
// another script, e.g., a controller
// Create the model
$admin = ORM::factory('Admin');
// $data = the data as an array, before serialization ...
$extra_validation = Validation::factory($data)
// add ->rule() calls here, but DO NOT chain ->check()
;
// Set $data in the model if it is going to be saved, e.g., $admin->data = $data;
// Set other data... e.g., $admin->foo = 'bar';
// Save the model
try {
$admin->save($extra_validation);
}
catch (ORM_Validation_Exception $e)
{
// Manipulate the exception result
}
While in this example you must still create another validation object, you are now able to catch all exceptions in a single block. I would recommend using var_dump() or similar on $e->errors() to check the namespace if you are using i18n messages to provide a human-readable error message. You should find that a namespace called "_external" has been created in the response.