Yii Custom Validator creation - yii

I am new to Yii framework, so please any one help me for this question
How to create custom validator class for the following validation,
I am having one table list , it is having listname and types on it, my validator want to check the list name to be unique for the specific type, for example,
listName Type
test1 1
test2 1
test3 2
when I insert a new list name validator, I want to retrieve the listnames and type and provide error if it is not unique for the specific type.

Well creating a custom validator method is really simple if that's what you are looking for.
You need a to add an element to rules() method in your Model (i.e protected/model/youTableName.php)
return array(
array('listName', 'uniqueForType', 'type')
);
Create a method to youTableName.php named by the validator name.
public function uniqueForType($field, $params)
{
$filedToCompare = $this->$field;
$fieldToCompareWith = $this->$params;
// Do your checking and comparing
if($weHaveAnError) {
$this->addError($field, printf("This listName already exists for type %s", $this->$params));
}
}
That should do the trick. Got that information form http://www.yiiframework.com/forum/index.php/topic/20399-conditional-validation-rule/

Related

How to get a column relationship based on field

I have a chat table that both a user and admin can chat the table is defined as follow:
id, from_id, to_id, message, is_from_admin.
what I want is, if the is_from_admin is true laravel should use the admin table at sql level for the from. otherwise it should use the user table for from and same applies to the to field. Thanks
If you have the chance, I'd rework the table a bit and name it like so:
id, from_user_type, from_user_id, to_user_id, message
The pair from_user_type and from_user_id can be used to creat a custom polymorphic relation ("type" refers to the model/table name, and "id" refers to the id of a row in this table) as seen here: https://laravel.com/docs/9.x/eloquent-relationships#one-to-many-polymorphic-relations .
If you also want to send admin-to-admin, you should also add to_user_type, to_user_id so you can create a polymorphic relationship on the receiving side as well.
The polymorphic relation will look something like this:
class ChatMessage
{
public function fromUser()
{
// This function should automatically infer `from_user_type` and `from_user_id`
// from this function name.
return $this->morphTo();
}
}
class AdminUser
{
public function chatMessages()
{
return $this->morphMany(ChatMessage::class, 'fromUser');
}
}
Laravel can not solve what you are doing, which is a polymorphic relationship, based on a boolean. Theoretically you could bind the polymorphic class definition to 0 or 1, but this is a hack at best. Alternatively you could rewrite your table structure to support polymorphic relations.
Instead i would say you achieve something that is working, with what you have. Create two relationships combined with some logic in an accessor. Create a relationship for the admin and for the user.
Chat extends Model
{
public function fromAdmin()
{
return $this->belongsTo(Admin::class, 'from_id')->where('is_from_admin', true);
}
public function fromUser()
{
return $this->belongsTo(User::class, 'from_id')->where('is_from_admin', false);
}
}
Now create the accessor on the Chat model, using your new relationships.
public function getFromAttribute()
{
return $this->fromAdmin ?? $this->fromUser;
}
With this approach, you should be able to access the attribute like this.
Chat::find(1)->from; // either a user or admin based on the data.

Jackson deserialization: How to get a default value even if the JSON property was null

In my project I'm using Jersey 2.23.1 with Jackson for JSON support.
When I'm getting a request with something like { "foo":null, "bar":"123" } as JSON, matching with class A{String foo; String bar;} Jersey first creates and instance of A (with default values if specified in constructor), then deserialize JSON to a temporary object A', then copies all JSON fields that were specified in JSON from A' to A. If I have default values in A-class constructor, and have fields equal to null in JSON, all my default values are erased and replaced by null. So in the example above, if I have a default value for the foo field, it will be replaced by null in the object Jersey will return as param for my #Path annotated method.
I'm using #JsonInclude(Include.NON_NULL) on A class to avoid the transfer of null fields during Response. But it only works for serialization, what about deserialization? I mean, when having { "foo":null } as JSON results in field "foo" = null in new object instance after deserialization.
Here is some code to sum all of this :
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(value = Include.NON_NULL)
public class User {
public enum EUserRole {
PARENT, STUDENT, PROF, ADMIN
}
#Id
public String id;
public String firstName;
public String lastName;
public EUserRole role;
public User() {
id = ObjectId.get().toString();
role = EUserRole.STUDENT;
lastName = "RandomLastName";
}
}
if I'm passing this kind of JSON
{
"id":null,
"lastName":null,
"firstName":"Random First Name",
"role":"STUDENT"
}
to my method (in controller)
#POST
public Response createUser(final User entity) {
}
it results that all null fields in JSON are set to null in my entity and not set to the constructor default values.
Do you know if there is a way to specify Jackson to ignore null fields during deserialization? Or is this a Jersey-related behavior?
There is no way to ignore data from JSON payload in that sense, based on value contained (you can use ignoral to just ignore all values for given property).
So if you want to avoid null assignment, you need define a setter that will just swallow null value (that is, only assign non-null).
Ability to prevent null assignment might a useful feature to add via #JsonFormat.Feature, something like:
// hypothetical no such feature exists yes
#JsonFormat(without = JsonFormat.Feature.ALLOW_NULL_ASSIGNMENT)
so perhaps this could be a feature request.
And the reason I think this belongs to per-property handling is that this seems like a specific rule for some of the properties. Although perhaps there could also be a matching global setting if it seems users really like such null-ignoral.

How to change the format of an attribute in a CActiveRecord returned by a findAll method?

I have an attribute in my model that is stored in binary format inside database.
In case the attribute is a geometric( polygon ) object.
This object can be casted to several string representations.
So how can I attach an event after a find execution that allows me to change the attribute on returned set only?
My first guess was to use the onAfterFind event but it is not calling the handler with the created element as documentation suggests. My first attempt was the following in the controller.
// an activeRecord class
GeoTableBinaryData extends CActiveRecord {
... // normal active record with a table which has a binary attribute called geom
}
$model = GeoTableBinaryData::model();
$model->onAfterFind->add(
function( CEvent $evt ){
// get the finded object to update the geom attribute on the fly here want
// a text representation in other case would transform it to XML or JSON
}
);
foreach ( $model->findAll() as $geoInfo )
{
... // output serialized geometry
}
The correct way of doing this, is that in your model have a afterFind method like:
protected function afterFind()
{
$this->someAttribute = $this->methodToChangeTheAttribute($this->someAttribute);
return parent::afterFind();
}
and that's all, when you will use AR's methods, every found model will pass through afterFind() and alter the someAttribute as you want.
You can also write getters for your different formats:
public function getGeoAsString()
{
// Create the string from your DB value. For example:
return implode(',', json_decode($this->geom));
}
Then you can use the geoAsString like a regular (read-only) attribute. You can also add a setter method, if you want to make it writeable.

NHibernate ExcludeProperty: Why is there no IncludeProperty

The Example class is great if you are specifying which properties you want to exclude from the example. But what if you want to specify which properties to include?
Take this example: looking for people in the database that have the same name.
A Person object has many properties. So to use the NHibernate.Criterion.Example object I would have to specify every field to exclude - which could be many.
Why is there no IncludeProperty method?
I have a Person object and I want to see if it is a duplicte based on pre-set business rules (FirstName, LastName, DateOfBirth). These rules could be changed to include a postcode or something else - and I'd like to make that configurable.
Is there an easy way around this?
I have a solution to the IncludeProperty issue:
private Type persitentType = typeof(T);
public IList<T> GetByExample(T exampleInstance, params string[] propertiesToInclude)
{
// get the properties that will be excluded
List<string> propertiesToExclude =
persitentType.GetProperties().Where(p => propertiesToInclude.Contains(p.Name) == false).Select(p => p.Name).ToList();
// create the criteria based on the example and excluding the given properties
ICriteria criteria = NHibernateSession.CreateCriteria(persitentType);
Example example = Example.Create(exampleInstance);
foreach (string propertyToExclude in propertiesToExclude)
{
example.ExcludeProperty(propertyToExclude);
}
criteria.Add(example);
// return the result
return criteria.List<T>();
}
Add this method to your repository class. It uses reflection to determine what properties the specified object has, and then finds the properties to exclude based on those that have been specified as includes.

ASP.NET MVC 2 RC model binding with NHibernate and dropdown lists

I have problem with model binding in my ASP.NET MVC 2 RC application that uses NHibernate for data access. We are trying to build the application in a Ruby on Rails way and have a very simple architecture where the domain entities are used all the way from the database to the view.
The application has a couple of domain entities which can be illustrated by the following two classes:
public class Product {
...
public Category Category { get; set; }
}
public class Category {
public int Id { get; set; }
public string Name { get; set; }
}
In the view that renders the edit form has the following statement to display a dropdown list:
<%= Html.DropDownListFor(model => model.Category.Id,
new SelectList(ViewData["categories"] as IList<Category>, "Id", "Name"),
"-- Select Category --" ) %>
Please disregard the use of "non-typed" view data to hold the category collection.
The action method that receives the form post is similar to to the following. Note that the TransactionFilter attribute adds NHibernate transaction handling and commits the transaction if no exceptions occur and validation succeeds.
[HttpPost]
[TransactionFilter]
public ActionResult Edit(int id, FormCollection collection) {
var product = _repository.Load(id);
// Update the product except the Id
UpdateModel(product, null, null, new[] {"Id"}, collection);
if (ModelState.IsValid) {
return RedirectToAction("Details", new {id});
}
return View(product);
}
My issue is that the product.Category.Id is set with the value selected in the form, e.g. Category.Id = "2". Using the default model binder results in the following type of NHibernate exception:
identifier of an instance of Name.Space.Entities.Category was altered from 4 to 2
That makes a lot of sense since the product already has a category assigned and only the primary key of that existing category is being changed. Another category instance should have been assigned instead.
I guess a custom ModelBinder can be created to handle this issue but is there a simpler way to make this work? Can (and should) the domain entities be modified to handle this?
I solved a similar problem with combo boxes on my edit page by changing the following line
#Html.DropDownListFor(x => x.Employee.Id, new SelectList(ViewBag.Employees, "Id", "DisplayName"))
by
#Html.DropDownListFor(x => x.Employee, new SelectList(ViewBag.Employees, "Id", "DisplayName"))
So I removed the '.Id' like Bryan suggested. Before the change, the model only contained the Id of the Employee and after the change, the Binder also loaded all the details of the employee into the model.
I've used similar techniques with Linq to SQL classes before with no problems. I don't think you'd need a custom ModelBinder for this. UpdateModel should be updating the Product class you are passing into it - not the Category sub-class attached to it. Check the html generated by the DropDownListFor helper. What is the name of the element? It should be the name of the foreign-key field in your Products table (e.g. "CategoryID" or "Product.CategoryID" not "Category.Id"). If it's "Category.Id" - try changing the first parameter of the DropDownListFor to either "model => model.Category" or "model => model.CategoryID" (or whatever the foreign key field is). This should cause UpdateModel to only update the foreign-key field in the Product class and not the Category class ID.
The solution we chose at the time was something similar to this:
TryUpdateModel(product, null, null, new[] {"Category"}, collection);
int categoryId;
if (int.TryParse(collection["Category.Id"], NumberStyles.Integer, CultureInfo.InvariantCulture, out categoryId) && categoryId > 0) {
product.Category = _categoryRepository.Load(categoryId);
}
else {
product.Category = null;
}
We simply tell the model binder to exclude the association property and handle that manually. Not pretty but worked at the time....