I have a table Friend (PersonA, PersonB). These are foreign keys of Person(id, name).
I want to create a Yii relation between them. This is what I have come up with:
public function relations() {
return array(
'friends1' => array(self::HAS_MANY, 'Friend', 'PersonA'),
'friends2' => array(self::HAS_MANY, 'Friend', 'PersonB'),
);
}
Is there a way to combine these two relations into one? I was hoping for something like this:
public function relations() {
return array(
'allFriends' => array(self::HAS_MANY, 'Friend', 'PersonA, PersonB'),
);
}
Any ideas?
EDIT #1:
For completeness, let's also imagine that I want to order friends1 and friends2 like this:
public function relations() {
return array(
'friends1' => array(self::HAS_MANY, 'Friend', 'PersonA', 'order'=>'id ASC'),
'friends2' => array(self::HAS_MANY, 'Friend', 'PersonB', 'order'=>'id ASC'),
);
}
This solution worked for me :
Override the __get function in your model:
public function __get($name)
{
if(($name == 'friends1') || ($name == 'friends2')) {
return parent::__get('friends1') + parent::__get('friends2');
}
else
return parent::__get($name);
}
I had the exact same thing come up in something I was working on. What I did is I created another function to compile the two relations into one array. The reason for this is because even if you were to get the two relationships combined, each time you would still need to test to see if PersonA or PersonB is the current user's id and use the other id to pull the Friends info. Here is the function I created in my model:
public function getFriends() {
$all_friends = array();
foreach($this->friends1 as $friend) {
if($friend->PersonA == $this->id) {
$all_friends[] = $friend->PersonA;
} else {
$all_friends[] = $friend->PersonB;
}
}
foreach($this->friends2 as $friend) {
if($friend->PersonA != $this->id) {
$all_friends[] = $friend->PersonA;
} else {
$all_friends[] = $friend->PersonB;
}
}
return $all_friends;
}
Another options:
public function getFriends() {
$criteria = new CDbCriteria;
$criteria->compare('PersonA',$this->id);
$criteria2 = new CDbCriteria;
$criteria2->compare('PersonB',$this->id);
$criteria->mergeWith($criteria2,'OR');
$friends = Challenge::model()->findAll($criteria);
return $friends;
}
Then for any Person you can just say:
$person = Person::model()->findByPk(1);
$friends = $person->friends;
Or instead of an array you could send back an CActiveDataProvider that you could do sorts and other things with. You could do this:
public function getFriends() {
$criteria = new CDbCriteria;
$criteria->compare('PersonA',$this->id);
$criteria2 = new CDbCriteria;
$criteria2->compare('PersonB',$this->id);
$criteria->mergeWith($criteria2,'OR');
return new CActiveDataProvider('Friend', array(
'criteria' => $criteria,
));
}
Then since you have CActiveDataProvider you can sort:
$friends = $user->friends->getData(); //not sorted or anything
//or you can manipulate the CActiveDataprovider
$data = $user->friends;
$data->setSort(array(
'defaultOrder'=>'PersonA ASC',
));
$friends = $data->getData();
Related
I'm creating a Restful application, so I'm recieving a POST request that could seem like this
$_POST = array (
'person' => array (
'id' => '1',
'name' => 'John Smith',
'age' => '45',
'city' => array (
'id' => '45',
'name' => 'London',
'country' => 'England',
),
),
);
I would like to save my person model and set its city_id.
I know that the easiest way is to set it manually with $person->city_id = $request['city']['id]; but this way isn't helping me....this code is only an example, in my real code, my model has 15 relationships
Is there any way to make it in a similar such as $person->fill($request);?
My models look like:
City
class City extends Model {
public $timestamps = false;
public $guarded= ['id'];//Used in order to prevent filling from mass assignment
public function people(){
return $this->hasMany('App\Models\Person', 'city_id');
}
}
Person
class Person extends Model {
public $timestamps = false;
public $guarded= ['id'];//Used in order to prevent filling from mass assignment
public function city(){
return $this->belongsTo('App\Models\City', 'city_id');
}
public static function savePerson($request){//Im sending a Request::all() from parameter
$person = isset($request['id']) ? self::find($request['id']) : new self();
$person->fill($request);//This won't work since my $request array is multi dimentional
$person->save();
return $person;
}
}
This is a bit tricky, but you can override fill method in your model, and set deeplyNestedAttributes() for storing attributes thats will be looking for in the request
class Person extends Model {
public $timestamps = false;
public $guarded= ['id'];//Used in order to prevent filling from mass assignment
public function city(){
return $this->belongsTo('App\Models\City', 'city_id');
}
public static function savePerson($request){//Im sending a Request::all() from parameter
$person = isset($request['id']) ? self::find($request['id']) : new self();
$person->fill($request);//This won't work since my $request array is multi dimentional
$person->save();
return $person;
}
public function deeplyNestedAttributes()
{
return [
'city_id',
// another attributes
];
}
public function fill(array $attributes = [])
{
$attrs = $attributes;
$nestedAttrs = $this->deeplyNestedAttributes();
foreach ($nestedAttrs as $attr) {
list($relationName, $relationAttr) = explode('_', $attr);
if ( array_key_exists($relationName, $attributes) ) {
if ( array_key_exists($relationAttr, $attributes[$relationName]) ) {
$attrs[$attr] = $attributes[$relationName][$relationAttr];
}
}
}
return parent::fill($attrs);
}
}
AR model Player:
public function scopes()
{
return array(
'proleague' => array(
'condition' => 'mode = "proleague"',
),
'main' => array(
'condition' => 'mode = "main"',
),
);
}
Using model Player:
Player::model()->
proleague()->
with('startposition')->
findAllByAttributes(... here some condition ...);
^^^ That's all ok. Scope-condition will be executed. But...
In my project I have many places where any scope for Player model doesn't specified and in this cases I need use this scope-condition as default:
'main' => array(
'condition' => 'mode = "main"',
)
If I add defaultScope() method to Player model like this
public function defaultScope()
{
return array(
'condition' => 'mode = "main"',
);
}
the next code
Player::model()->
proleague()->
with('startposition')->
findAllByAttributes(... here some condition ...);
won't run correct. I won't get mode = "proleague" condition, becouse I'll use defaultScope() with mode = "main".
Any suggestions? How can I resolve the problem?
You should just use the resetScope(true) method. It "removes" the defaultScope filter.
$model = Player::model()->resetScope(true)->proleague();
create a new Class for this.
<?php
## e.g. protected/models/
class MyCoreAR extends CActiveRecord
{
/**
* Switch off the default scope
*/
private $_defaultScopeDisabled = false; // Flag - whether defaultScope is disabled or not
public function setDefaultScopeDisabled($bool)
{
$this->_defaultScopeDisabled = $bool;
}
public function getDefaultScopeDisabled()
{
return $this->_defaultScopeDisabled;
}
public function noScope()
{
$obj = clone $this;
$obj->setDefaultScopeDisabled(true);
return $obj;
}
// see http://www.yiiframework.com/wiki/462/yii-for-beginners-2/#hh16
public function resetScope($bool = true)
{
$this->setDefaultScopeDisabled(true);
return parent::resetScope($bool);
}
public function defaultScope()
{
if(!$this->getDefaultScopeDisabled()) {
return array(
'condition' => 'mode = "main"',
);
} else {
return array();
}
}
}
In your code:
// no default scope
$model = Player::model()->noScope()->proleague();
// with default scope
$model = Player::model()->proleague();
What I'm wondering is:
is it possible in Yii to add some kind of property in a Model, so only items with the property isdeleted set as 0 are shown?
So I'm looking for a way, Yii would just ignore these instances of the items...
Something like:
public function rules()
{
return array(
...
array('isdeleted', 'shouldEqualTo=>0'),
...
);
}
I thought messing around with rules() would be a way, but it doesn't work or I am doing it wrong...
You should use scopes() for that.
public function scopes()
{
return array('active' => array('condition' => 'isdeleted = 0'));
}
Then
$active = MyModel::model()->active()->findAll();
EDIT:
If you want to make the filter default, implement defaultScope() function:
public function defaultScope()
{
return array('condition' => 'isdeleted = 0');
}
Thanks to W.B.'s answer I knew to look into scopes, you can use scopes like W.B. did:
public function scopes()
{
return array('active' => array('condition' => 'isdeleted = 0'));
}
and then use
$active = MyModel::model()->active()->findAll();
If you do not want to change your code in your project (like me) you can use:
public function defaultScope()
{
return array(
'condition' => 'isdeleted = 0',
);
}
and then use
$active = MyModel::model()->findAll();
I have the following table structure.
tb_posts has the field author_id which relates to tb_author.id
in YII i have the following in my posts activeRecord
public function relations()
{
return array(
'authorRelation' => array(self::BELONGS_TO, 'authorRecord', 'author')
);
}
How i do a search of posts of an authors with name 'foo'? I am trying the following with no success
$criteria=new CDbCriteria;
$criteria->with = array('authorRelation');
$criteria->together = true;
$criteria->compare( 'author.name', 'foo', true );
$posts=PostsRecord::model()->findAll($criteria);
Set table alias for your models at init.
class PostsRecord extends CActiveRecord
{
// ...
public function init() { $this->setTableAlias( 'postsrecord' ); }
// ...
}
class AuthorRecord extends CActiveRecord
{
// ...
public function init() { $this->setTableAlias( 'authorrecord' ); }
// ...
}
Finally:
$condition=new CDbCriteria;
$condition->with = array('authorRelation');
$condition->together = true;
$condition->condition = 'authorrecord.name=:authorname';
$condition->params = array( ':authorname' => 'foo' );
$posts=PostsRecord::model()->findAll($condition);
Your relation should be 'authorRelation' => array(self::BELONGS_TO, 'authorRecord', author_id'). The 3rd parameter is the foreign key.
The second part of the code doesn't have any errors, the search should work if you set up the relations correctly.
Following Models:
class User extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn ( 'iron', 'integer', 4 );
}
public function setUp() {
$this->hasMany ('Field as Fields', array(
'local' => 'id',
'foreign' => 'owner_id'
));
}
}
class Field extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('owner_id','integer',4);
$this->hasColumn('ressource_id','integer',4);
$this->hasColumn('ressource_amount','integer','2');
}
public function setUp() {
$this->hasOne('User as Owner',array(
'local' => 'owner_id',
'foreign' => 'id'
));
}
}
And I try following DQL:
$sqlRessourceUpdate = Doctrine_Query::create()
->update('Field f')
->set('f.Owner.iron','f.Owner.iron + f.ressource_amount')
->where('f.ressource_id = ?',1);
Result:
'Doctrine_Query_Exception' with message 'Unknown component alias f.Owner'
Basicly I just want to update the "iron" attribute from the Field-Owner according to the fields' value
I am guessing you can't reference other tables like that in your query.
This may not be the best way but, here is what I do
$q = Doctrine_Query::create()
->select('*')
->from('Field')
->where('ressource_id = ?',1); //btw resource has one 's'
$field = $q->fetchone();
$field->Owner['Iron'] += $field->ressource_amount;
$field->save();
EDIT:
Actually I don't know if that will work... this is more like what I do
$q = Doctrine_Query::create()
->select('*')
->from('Field')
->where('ressource_id = ?',1); //btw resource has one 's'
$field = $q->fetchone();
$user = $field->Owner;
$user['Iron'] += $field->ressource_amount; // I have never used a += like this, but in theory it will work.
$user->save();