Symfony2 constraints entity validation yml - validation

I've a many to many relation (user and account). In the user entity, I've private property groups (array collection).
When I try to validate this property (groups) with a simple "NotBlank", it's not work. So I try this below (collection and choice).
I read this http://symfony.com/fr/doc/2.3/reference/constraints/Choice.html
and this http://symfony.com/fr/doc/2.3/reference/constraints/Collection.html but it doesn't work or I don't correctly use them.
Can anybody gives me some help ?
/* USER accounts property
...
/**
* #ORM\ManyToMany(targetEntity="Account", mappedBy="users", cascade={"persist", "remove"})
*/
private $accounts;
...
Than the userType
...
->add('accounts', 'genemu_jqueryselect2_entity', array(
"class" => "CMiNewsBundle:Account",
"property" => "name",
"multiple" => "true",
"query_builder" => function (EntityRepository $er) use ($user)
{
return $er->createQueryBuilder('acc')
->join('acc.users','u')
->where('u.id = :userID')
->setParameter('userID' , $user);
}
)
)
...
The validation.yml
CM\Bundle\iNewsBundle\Entity\User:
properties:
...
accounts:
- NotBlank: ~
...

"NotBlank" assert checks if the property === null || property === ''. Since your property is a collection, you probably initialise it as an ArrayCollection in your constructor so it will never be null.
For collections you should use the "Count" assert
http://symfony.com/doc/current/reference/constraints/Count.html
It forces you to set the "maximum" count as well as the minimum so you might want to create your own assert.

Related

Eager Loading return null in Laravel

I have a issue with Eager Loading
I have two models :
class Provider extends Model
{
/**
* Get the currency record associated with the provider.
*/
public function currency()
{
return $this->hasOne('App\Currency', 'tabcur', 'poucur')->where('tabcol','$DEV');
}
class Currency extends Model
{
/**
* Get the providers record associated with the currency.
*/
public function provider()
{
return $this->belongsTo('App\Provider', 'tabcur', 'poucur');
}
When i try this :
Provider::first()->currency
It's works
But, if i try this :
Provider::with('currency')->first()
The field currency is null
currency: null,
Can anyone help me ?
EDIT 1
I tried this
>>> DB::connection()->enableQueryLog()
>>> App\Provider::with('currency')->first()
>>> DB::getQueryLog()
I have this with App\Provider::with('currency')->first()
[
[
"query" => "select * from PROVIDER FETCH FIRST 1 ROWS ONLY",
"bindings" => [],
"time" => 18.74,
],
[
"query" => "select * from CURRENCY where tabcol = ? and CURRENCY.tabcur in (?)",
"bindings" => [
"$DEV",
" ",
],
"time" => 33.94,
],
]
But it's steel "null"
What first() do is:
The first method returns the first element in the collection that passes a given truth test.
So when the Eager loading test is passed, It returns the >>First Record<< with the relation loaded.
But
Your Relation currency is returning empty because the where condition in your currency relation is not passed so it will show that the the relation is loaded with with but it won't show any records because it didn't pass the where.
This happened because the first record that met the with condition did not pass the where condition, try to remove the where then load the relation again and see what is the value of tabcol, then do it again but this time with where but put the value you found for that returned record and it should work.
Provider::first()->currency is gonna return a Currency::class
Provider::with('currency')->first() will return a Provider::class with Currency::class inside the relations[]
I don't see any reason why your code would not work.

How do you set default attribute values for a custom pivot table?

I am trying to set default values for my pivot table intermediate attributes.
I would expect the $attributes property to work with Pivot classes the same as it does with Model classes, since Pivot extends Model. However that does not seem to be the case, because the property seems to have no effect and the defaults are never assigned.
class Client_orm extends Model
{
public function ranks()
{
return $this->belongsToMany( 'Rank_orm', 'diy_client_rank', 'client_id', 'rank_id' )
->using( 'Client_rank_orm' )
->withPivot( 'thedate', 'notes' );
}
}
class Rank_orm extends Model
{
public function clients()
{
return $this->belongsToMany( 'Client_orm', 'diy_client_rank', 'rank_id', 'client_id' )
->using( 'Client_rank_orm' )
->withPivot( 'thedate', 'notes' );
}
}
class Client_rank_orm extends Pivot
{
protected $attributes = array(
'thedate' => 'some default value',
'notes' => 'some default value',
);
}
I would expect that with this code, any omitted pivot table attributes would automatically be set with the defaults. However, I am getting the following Illuminate\Database\QueryException when the attributes are omitted:
General error: 1364 Field 'thedate' doesn't have a default value
I realize I could alter the SQL structure to accept NULL values or set defaults, but that would be a last resort if there is no other way to set defaults through model definitions.
Any help would be appreciated!
Turns out this one still hasn't been implemented.
There is a closed proposal for this: https://github.com/laravel/ideas/issues/872
Currently you can only use wherePivot() feature which actually sets default attributes but it's main purpose is filtering, so you still can not set defaults without filtering which is pretty weird as for me.
https://github.com/laravel/framework/pull/22867

yii2 Validation not working

I need to validate a value from another table both of them field type decimal
if amount(model 1) moreover available(model2 DATA selected)
model 1
rule
public function rules()
{
return [
[['amount'], 'checkAvailable','skipOnEmpty' => false],
];
}
custom validation function
public function checkAvailable($attribute, $params)
{
$caid = $this->capex_budget_id;
$available = Capexbudget::find()
->select(['available'])
->where('id = :caid', [':caid' => $caid])
->all(); // select amount from Capexbudget where id = $caid
if ($this->amount > $available){
$this->addError('amount', 'not enough');
}
}
i can got a data available from Capexbudget where ID that i select
here are the query logs
but validation not working it not compare value between $this->amount and $available
what i missing please.
First of all, you're selecting all records matching your where conditions here:
$available = Capexbudget::find()
->select(['available'])
->where('id = :caid', [':caid' => $caid])
->all(); // HERE
You should change function all() to one(), to get one record.
Second thing is, if you're using all() or one(), that methods returns object, not value.
To make it work, you should change if statement to:
if ($this->amount > $available->available){ //here, compare to attribute `available`

Eloquent casting attributes to Collections unexpected behaviour

Q1. I have an Eloquent model that casts an attribute to a Collection.
Calling Collection's method on this attribute doesn't affect the model values. Eg: put()
When using Collections , iam able to do this :
$var = collect();
$var->put('ip', '127.0.0.1');
var_dump($var);
Output as expected :
object(Illuminate\Support\Collection)[191]
protected 'items' =>
array (size=1)
'ip' => string '127.0.0.1' (length=4)
But when i use with a casted attribute on a Eloquent model, this doesn't work as expected
$user = App\User::create(['email'=>'Name', 'email'=>'mail#example.com', 'password'=>bcrypt('1234')]);
$user->properties = collect();
$user->properties->put('ip', '127.0.0.1');
var_dump($user->properties);
object(Illuminate\Support\Collection)[201]
protected 'items' =>
array (size=0)
empty
This doesn't populate the field.
I think that another collection is created, so to work as expected i must assign this new collection to my field.
Like so :
$user->properties = $user->properties->put('ip', '127.0.0.1');
Q2. Is there a proper way to initialize collection of the field by default (create an empty collection if the field is null), without having to call $user->properties = collect(); "manually" every time?
User.php
class User extends Authenticatable
{
protected $casts = [
'properties' => 'collection',
];
...
}
Migration file
Schema::table('users', function($table) {
$table->text('properties')->nullable();
});
Q1: an attribute casted to collection has a getter that returns, each time, a new BaseCollection that is constructed on the value of the attribute.
As already supposed the getter returns another collection instance and every direct change on it does not change the value of the attribute but instead the newly created collection object.
As also pointed by you the only way to set a a collection casted attribute is to assign it his own original value merged with new ones.
So instead of put() you have to use:
$user->properties = $user->properties->put('ip', '127.0.0.1');
// or
$user->properties = $user->properties ->merge(['ip'=>'127.0.0.1'])
Q2: We have to think that the database representation is a text; so IMHO the proper way to initialize a Model in the migration is to give it a default empty json, i.e.:
$table->text('properties')->default('{}');
But this works only for models created without setting the property field and retrieved after.
For a newly created Model my advice is to pass a default void array, i.e.:
App\User::create([
'name'=>'Name',
'email'=>'mail#example.com',
'password'=>bcrypt('1234'),
'properties' => []
]);
In addition to dparoli's outstanding answer, it is also possible to add a default value through Laravel's boot method, which is available on every Model.
Something like the following example code
protected static function boot()
{
parent::boot(); //because we want the parent boot to be run as well
static::creating(function($model){
$model->propertyName = 'propertyValue';
});
}
You can play with this approach if you like as well.

Yii2 Activerecord get fields from junction table and order by according to them

I have items and units table that have many to many relationship. In other words, the item has many units and the unit has many items. I managed the relation through a junction table item_units. The junction table has some extra field more than item_id and unit_id, i.e it has price, and weight (it is an integer to manage the order of units for each item for display purposes).
I managed the relations in the models as follows:
//In Items model
/**
* #return \yii\db\ActiveQuery
*/
public function getItemUnits()
{
return $this->hasMany(ItemUnits::className(), ['item_id' => 'id'])->orderBy(['item_units.weight' => SORT_DESC]);
}
public function getUnits()
{
return $this->hasMany(Units::className(), ['id'=> 'unit_id'])->select(['id','title'])->via('itemUnits');
}
//
//In Units model
public function getItemUnits()
{
return $this->hasMany(ItemUnits::className(), ['unit_id' => 'id'])->orderBy(['price' => SORT_DESC]);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getItems()
{
return $this->hasMany(Items::className(), ['id' => 'item_id'])->via('itemUnits');
}
//
//In ItemUnits model
public function getItem()
{
return $this->hasOne(Items::className(), ['id' => 'item_id']);
}
/**
* #return \yii\db\ActiveQuery
*/
public function getUnit()
{
return $this->hasOne(Units::className(), ['id' => 'unit_id']);
}
In the controller I'm able to get the data of all related units to an item by something like the following:
$item = Items::findOne($id);
return Json::encode($item->units);
The following is a demo of the JSON object obtained:
[{"id":"4","title":"قرص"},{"id":"5","title":"شريط 10"},{"id":"6","title":"علبة 2 شريط"}]
However, I could not able to order the results according to the weight field in item_units table and also I could not able to include the price field there in the demo result above -JSON Object-.
I only able to get data in item_units as a separate result like the following:
return Json::encode($item->itemUnits);
Update
According to the two answers (#Александр Шалаев & #Onedev.Link) , I have overridden the fields method in Units model as follows:
public function fields() {
parent::fields();
return [
'price' => function($model){
return $model->id; //Here I could not able to get the corresponding price field value from item_units -junction table-
},
'id',
'title',
];
}
However, I could not able to get the price field value from the junction table, temporary, I set it to current model id to prevent error generation. Also, I still has no any mean to set order by using weight field in that junction table.
Update 2
In other words, how could Yii2 Activerecords perform the following SQL query:
SELECT units.id UnitID, units.title Unit, iu.weight, iu.price
FROM units
Left JOIN item_units AS iu
ON iu.item_id = 1 AND iu.unit_id = units.id
WHERE
units.id = iu.unit_id
ORDER BY iu.weight;
Finally I have found a solution. It depends on findBySql method. I'm going to use the above SQL query regarded in Update 2 -just I have removed some selected fields to be suitable for my current task-.
public function actionUnitsJson($id){
$sql = 'SELECT units.id, units.title
FROM units
Left JOIN item_units AS iu
ON iu.item_id = :id AND iu.unit_id = units.id
WHERE
units.id = iu.unit_id
ORDER BY iu.weight DESC;';
$units = \common\models\Units::findBySql($sql,[':id' => $id])->asArray()->all();
return Json::encode($units);
}
You need fields or extraFields in your ActiveRecord model with asArray.
Example:
/**
* #return array
*/
public function fields()
{
return [
'itemUnit', //will get getItemUnit method
];
}
or
/**
* #return array
*/
public function extraFields()
{
return [
'itemUnits', //it is relation name
];
}
Usage:
$model->toArray(); //will contains fields and extra fields relations
... sort array & return
By default, yii\base\Model::fields() returns all model attributes as fields, while yii\db\ActiveRecord::fields() only returns the attributes which have been populated from DB.
You can override fields() to add, remove, rename or redefine fields. The return value of fields() should be an array. The array keys are the field names, and the array values are the corresponding field definitions which can be either property/attribute names or anonymous functions returning the corresponding field values.

Resources