I have a very simple relation defined as follows (aId and bId are the primary keys for each table).
class A extends CActiveRecord
{
// #var int32 $aId
}
class B extends CActiveRecord
{
// #var int32 $bId
// #var int32 $aId
public function relations()
{
return array(
'a'=>array(self::HAS_ONE, 'A', 'aId'),
);
}
}
As long as bId <= 5, I can access A through $bModel->a without any problems. What's strange is for bId > 5, $bModel->a is null. I've checked $bModel->aId for bId > 5 and the foreign key is correct. I can even access A with $aModel = A::model()->findByPk($bModel->aId);. I can also manually edit my bIds in the database table, which produces the same result.
I have no idea what's causing the relation to fail for primary key's greater than five. Any suggestions for troubleshooting? I'm at a loss.
EDITED
It turns out I wasn't using the relation properly. I should have used BELONGS_TO.
class B extends CActiveRecord
{
// #var int32 $bId
// #var int32 $aId
public function relations()
{
return array(
'a'=>array(self::HAS_ONE, 'A', 'aId'),
);
}
}
HAS_ONE was causing B to use bId to index A. Since I had five instances of A in my database that worked for bID < 5
Enable query logging in your application config to see what exactly is happening.
Do you get any results when manually running those queries?
'components' => array(
'db' => array(
(..)
'enableParamLogging' => true,
),
'log' => array(
'class' => 'CLogRouter',
'routes' => array(
// Show log messages on web pages
array(
'class' => 'CWebLogRoute',
'categories' => 'system.db.CDbCommand', //queries
'levels' => 'error, warning, trace, info',
//'showInFireBug' => true,
),
(I'd post this as a comment rather than an answer, but it seems I can't)
I recommend you to use this -> Yii Debug Toolbar (it is created by my friend here in Ukraine).
Can you provide mysql structure + some example data. Thanks.
Related
Why are my global belongsTo conditions defined within a model not being applied?
I'm using CakePHP v2.4.2.
In model Order.php:
public $belongsTo = array(
...,
'Agent' => array(
'className' => 'Party',
'foreignKey' => 'agent_id',
'conditions' => array(...)
),
...
In controller OrdersController.php:
$agents = $this->Order->Agent->find('list');
In rendered view the following SQL statement is being applied:
SELECT `Agent`.`id`, `Agent`.`name` FROM `zeevracht2`.`parties` AS `Agent` WHERE 1 = 1;
I tried different conditions, but even a simple string containing true isn't being applied (while adding this condition to PartiesController.php within a $this->Order->Agent->find(); works fine:
$agents = $this->Order->Agent->find('list', array(
'conditions' => array('true')
));
leads to:
SELECT `Agent`.`id`, `Agent`.`name` FROM `zeevracht2`.`parties` AS `Agent` WHERE true;
After collaborating on IRC I found the answer on my own question.
It seems that conditions in the belongsTo condition of a model is only applied to the JOIN when querying an Order.
I was trying to filter Party on specific roles, e.g. Agent, which is an alias of a Party with a role as agent. So the association should be conditioned with a role set as agent. Ideally, this would automatically condition any $this->Order->Agent->find() calls. But unfortunately, this is not possible due to a technical issue which is being addressed in the development of version 3 of CakePHP.
The solution is to have two types of conditions on the association: one for the JOIN and one for the association itself.
Why one for the JOIN? E.g. when a Post belongs to User, but only post.validated should be displayed.
If you’re trying to find the Agent that a particular Order belongs to, then you should get the record back when querying orders. So something like:
<?php
class OrdersController extends AppController {
public function view($id) {
$order = $this->Order->findById($id);
pr($order); exit;
}
}
Should yield something like:
Array
(
[Order] => Array
(
[id] => 83
…
)
[Agent] => Array
(
[id] => 1
…
)
)
In your question where you then make an additional model call like $this->Order->Agent->find('list');, that’s making a new query, which will fetch all agents with no conditions. The conditions key, where you pass true, will have no affect, because that’s not a condition. Conditions should be an array, like this:
$this->Order->Agent->find('all', array(
'conditions' => array(
'Agent.id' => 1
)
));
But as I say, if your Order model belongs to your Agent model, then you should get an Agent result set back when you get your Order result set. If not, try adding the Containable behavior to your Order model:
<?php
class Order extends AppModel {
public $actsAs = array(
'Containable'
);
}
Try this:
public $belongsTo = array(
...,
'Agent' => array(
'className' => 'Party',
'foreignKey' => false,
'conditions' => array('joinField'=>'joinedField', ...more conditions)
),
I'm trying to insert into the table of a model with multiple levels of HasMany relationships. Here's the breakdown so far
Customer->(HasMany)->Members->(HasMany)->Incomes
However, on when trying to insert into the Incomes table, I get a "Not null violation" with the foreign key from the Members table not being carried to Incomes. I know the most common problem is screwing up the $_has_many and $_belongs_to properties, but as far as I can tell they are fine. Plus, Just inserting into the Member table works fine so I know at least for the first layer it's working! The only thing I can think of is if since it's a second level down, it's screwing up because of that. Here's my code:
Relation Link (Member)
protected static $_has_many = array(
'incomes' => array(
'key_from' => 'id',
'model_to' => 'Model_Income',
'key_to' => 'member_id',
'cascade_save' => true,
'cascade_delete' => true,
),
);
Relation Link (Income)
protected static $_belongs_to = array(
'member' => array(
'key_from' => 'member_id',
'model_to' => 'Model_Member',
'key_to' => 'id',
'cascade_save' => true,
'cascade_delete' => true,
),
);
The Controller Code
// code to set up $customer
$customer->members[] = Model_Member::forge();
// set $member_vals here
$customer->members[0]->set($member_vals);
$customer->members[0]->incomes[] = Model_Income::forge();
// set $income_vals here
$customer->members[0]->incomes[0]->set($income_vals);
$customer->save();
The first problem that comes to mind is that you're setting relations on ->members and ->incomes (plural), instead of ->member and ->income (singular).
I also prefer to do relations separately, instead of chaining them like you do. So, if you already have relations set when you're adding a new Member to Customer, or a new Income to Member, my guess is that the index will not be 0.
What if you try the following instead:
$member = Model_Member::forge($member_vals);
$income = Model_Income::forge($income_vals);
$member->income[] = $income;
$customer->member[] = $member;
$customer->save();
Does it work?
Different suggestion:
Since it appears that you're following the table/column naming convention, try only this:
// Model_Member
protected static $_has_many = array(
'incomes'
);
// Model_Income
protected static $_belongs_to = array(
'members' // plural, not singular like you seem to have
);
If you want more help, you should definitely show how you have the Member/Income models and tables.
I have followed a blog post to implement soft delete behavior in yii and it work well.
here's some snippet:
class <name of model> extends CActiveRecord {
public function behaviors {
return array(
'SoftDeleteBehavior' => array(
'class' => 'application.components.behaviors.SoftDeleteBehavior',
'deleteAttribute' => 'deleted_date', // optional, default is 'deleted_time'
'timestampExpression' => 'date("Y-m-d H:i:s");',
),
);
}
}
How do I update multiple deleteAttribute from joining table using SoftDeleteBehavior?
In my controller I want to set a variable(A) which has a one to many relationship with another model(B) which has a HABTM (has and belongs to many) relationship with ANOTHER model(C).
Currently when I set the variable in the controller I can access the model(B) in the view, but not ITS dependancies (model C):
//A's controller...
public function admin_view($id = NULL) {
$A = $this->A->findById($id);
$this->set('A', $A);
}
Here's what I see if I dump the variable in the view:
//A's admin_view.ctp...
//debug($A);
array(
'A' => array(
'id' => '1',
'name' => 'Name',
'created' => '2013-04-04 15:25:54',
'modified' => '2013-04-04 15:25:54'
),
'B' => array(
(int) 0 => array(
'id' => '1',
'created' => '0000-00-00 00:00:00',
'modified' => '2013-04-05 10:31:42'
),
(int) 1 => array(
'id' => '13',
'created' => '0000-00-00 00:00:00',
'modified' => '2013-04-05 10:31:42'
Is there a way to set the variable such the each "B" will have it's respective Cs included?
http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
// In your model
var $actsAs = array('Containable');
// In your controller
public function admin_view($id = NULL) {
$this->A->contain(array('B' => array('C')));
$A = $this->A->findById($id);
$this->set('A', $A);
}
That should give you what you need, setting recursive to 2 is the easy way out, but you'll get redundant data if you have other associations.
Well then, explaining $recursive and Containable behavior:
Recursive is a value between -1 to 2, and basically it tells the model "I want to fetch this record AND all records associated with it" if it's set to 2. If it's -1, then with find or read you only get that model data.
Containable let's you specify which models/fields-of-other-models you want to fetch (they have to have an association, though).
I've repeated many times I'm not a fan of $recursive != -1 because I feel it doesn't let you control what data you retrieve and when to do it. I recommend you use
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
//etc
}
in the AppMdel so everything isn't recursive and containable by default (it isn't necessary for the query to work, though). Then, regarding your question, the find query should be like
$A = $this->A->find('first', array('conditions'=>array('id'=>$id),
'contain'=>array('B-model'=>array('C-model'))));
I'm using php.activerecord, and I am trying to link tables together. I'm not using their structure, but php.activerecord assumes I am, so it doesn't always work. I'm trying to use it on an already made app, so I can't change the database.
I learned from my previous question - Model association with custom table and key names - that I need to be as explicit as possible with the primary_key and foreign_key fields.
I'm having issues now using has_many through. I keep getting NULL, and I have no idea why.
So, here's a scenario: I have 3 tables, contacts, contactPrefs, and preferences. Those tables are as follows
contacts
--------
contactID
name
status
contactPrefs
------------
contactID
prefID
prefValue
preferences
-----------
prefID
name
description
Each contact has multiple contactPrefs. Each contactPrefs has one preferences. I tried to use has_many to get this working, but it's not. Here are my models:
Contacts.php:
<?php
class Contact extends ActiveRecord\Model {
static $primary_key = 'contactID';
static $has_many = array(
array(
'prefs',
'foreign_key' => 'contactid',
'primary_key' => 'contactid',
'class_name' => 'ContactPref'
),
array(
'preferences',
'foreign_key' => 'prefid',
'primary_key' => 'prefid',
'through' => 'prefs',
'class_name' => 'Preference'
)
);
}
ContactPref.php:
<?php
class ContactPref extends ActiveRecord\Model {
static $table_name = 'contactPrefs';
static $belongs_to = array(
array(
'contact',
'foreign_key' => 'contactid',
'primary_key' => 'contactid'
),
array(
'preference',
'foreign_key' => 'prefid',
'primary_key' => 'prefid'
)
);
}
Preference.php:
<?php
class Preference extends ActiveRecord\Model {
static $primary_key = 'prefID';
static $has_many = array(
array(
'prefs',
'foreign_key' => 'prefid',
'primary_key' => 'prefid',
'class_name' => 'ContactPref'
)
);
}
According to the docs, I now should be able to the following:
<?php
var_dump(Contact::find(1234)->preference);
I cannot. I get NULL. Oddly, I can do this:
<?php
var_dump(Contact::find(1234)->prefs[0]->preference);
That works correctly. But, shouldn't I be able to access the preference object directly through the contact object? Am I misunderstanding the docs (they aren't the greatest, in my opinion)? Am I doing something wrong?
First you are reading the docs with a small flaw. In the docs you are shown:
$order = Order::first();
# direct access to users
print_r($order->users); # will print an array of User object
Which you are already doing via Contact::find(1234)->prefs. Let me boil it down a bit
$contact = Contact::find(1234);
# direct access to prefs
print_r($contact->prefs); # will print an array of ContactPref object
Second, what you actually want is undefined. What should Contact::find(1234)->preference actually do? Return the preference of the first ContactPref? Return an array of Preference objects?
I feel like offering both:
<?php
class Contact extends ActiveRecord\Model {
static $primary_key = 'contactID';
static $has_many = array(
array(
'prefs',
'foreign_key' => 'contactid',
'primary_key' => 'contactid',
'class_name' => 'ContactPref'
),
array(
'preferences',
'foreign_key' => 'prefid',
'primary_key' => 'prefid',
'through' => 'prefs',
'class_name' => 'Preference'
)
);
public function get_preference() {
return isset($this->prefs[0])
? $this->prefs[0]->preference
: null
;
}
public function get_preferences() {
$preference=array();
foreach($this->prefs as $pref) {
$preference[]=$pref;
}
return $preference;
}
}
Let me explain a little bit what I have done. The ActiveRecord\Model class has a __get($name) function that looks for another function called get_$name, where $name in your case is preference (for the first result) and preference (for the entire collection). This means you can do Contact::find(1234)->preference which would be the same as doing Contact::find(1234)->prefs[0]->preference (but safer, due to the check) and Contact::find(1234)->preferences to get the entire collection of preferences.
This can be made better or optimized in numerous ways, so please don't take it as it is, but do try and adapt it to your specific situation.
For example you can either use the id of the preference as an index in the array or either not force a load of more data from ContactPrefs than the ones you are going to use and try a more intricate query to get the preference objects that you specifically need.
If I find a better implementation by getting through to work in the relationship definition, I'll return. But seeing the Unit Tests for active record, I'm skeptical.
There are several things that look strange, so it's not easy to come to a "this will fix it" for you, but this is an issue at least:
Fieldnames should always be lower-case in phpactiverecord. SQL doesn't mind it either way (not that table names ARE case-sensitive, but column names aren't). So make this:
static $primary_key = 'contactID';
into
static $primary_key = 'contactid';
The connections // find commands can be used in SQL, in which case it doesn't really matter how your key-string is 'cased', so some stuff works. But if the connection goes trough the inner-workings of phpmyadmin, it will fail. So check out this contactID but also the prefID.
Again, this goes only for COLUMN names, so don't go changing classnames or table-names to lowercase.
(extra point: phpmyadmin has trouble with combined primary keys. So while it might be ugly, you could add an extra row to your contactprefs table (if you don't allready have it) called id, to make that table actually have something to work with. It wouldn't give you much trouble, and it would help the activerecord library a lot)
Try the following:
<?php
var_dump(Contact::find(1234)->preferences);
The documentation says that with a has_many relationship, it should be referenced by a plural (http://www.phpactiverecord.org/projects/main/wiki/Associations#has_many_through). The Contact::find(1234) returns a Contact object which has multiple contactPrefs with their each Preference. In addition, in your Contact model, you specify the has_many as preferences .
static $has_many = array(
array(
'prefs',
'foreign_key' => 'contactid',
'primary_key' => 'contactid',
'class_name' => 'ContactPref'
),
array(
'preferences',
'foreign_key' => 'prefid',
'primary_key' => 'prefid',
'through' => 'prefs',
'class_name' => 'Preference'
)
);
Edit Through Modification:
Try the following Contact model
<?php
class Contact extends ActiveRecord\Model {
static $primary_key = 'contactID';
static $has_many = array(
array(
'prefs',
'foreign_key' => 'contactid',
'class_name' => 'ContactPref'
),
array('preferences',
'through' => 'prefs',
'class_name' => 'Preference',
'primary_key' => 'prefID')
);
}