how to get CakePHP many-to-many result - php

I have a many-to-many relation between the models Image and Units using a images_units join table.
How can I translate this query to a cakePHP find()?
SELECT * FROM Image, Units, images_units WHERE images_units.unit_id = 29;
Right now I'm trying find() on Image->find('all', $params); with no luck.

Straight from the CakePHP Manual:
$this->Image->bindModel(array('hasOne' => array('ImagesUnit')));
$this->Image->find('all', array('fields' => array('Image.*'),'conditions' => array('ImagesUnit.unit_id' => 29)));
Of course, you will need to have the HABTM association defined in the model. See the whole section on HABTM for learning how to use it.

In your Image Model, add the following code:
$hasAndBelongsToMany = 'Unit';
The find() in your Images controller should look like this:
$this->Image->find('all', array('conditions'=>array('Unit.id'=>29)));
Still not quite sure this is what you're looking for but I think this is correct.

Related

CakePHP - Exclude Models from JOIN in find() method

I'm currently working on a project with CakePHP and its find() method to handle database queries.
My current situation is as follows:
I have 3 Models: User, Location and Order. The connection is that a User hasOne Location and User hasMany Orders.
When I'm using the find() method to get the User and the address (stored in the Location model) CakePHP returns the User, Location, and the Order Models. In my case, I don't need the Order information.
So my question is: Is it possible to tell CakePHP not to join with the Order Model?
I know about the recursive attribute, but if I set it to -1, CakePHP returns just the User Model and in the case of recursive >= 0, it returns all 3 Models.
Solved problem with Containable of cakephp. The code is as follows:
$this->User->Behaviors->load('Containable');
$this->User->recursive = -1;
$this->paginate = array('fields' => array('User.*'),
'contain' => array('Location'=>array('field1', 'field2')
);
$user = $this->Paginator->paginate('User');
You can use unbind model function to exclude desired models
In your case :
$this->User->unbindModel(array('hasMany' => 'Orders'));
For common
$this->User->unbindModel(
array(
'hasMany' => array('Model1','Model2'),
'hasOne' => array('Model1','Model2'),
'belongsTo' => array('Model1','Model2'),
)
);

Softdelete with Yii and relations

I have a simple DB with multiple tables and relationships, ie:
Article - Category
User - Group
etc...
I have implemented SoftDelete behavior where there is a Active column and if set to 0, it is considered deleted.
My question is simple.
How to i specify in as few places as possible that i only want load Articles that belong to Active categories.
I have specified relationships and default scopes (with Active = 1) condition.
However, when i do findAll(), it returns those Articles that have Active = 1, even if the category it belongs to is Active = 0....
Thank you
Implementation so far:
In base class
public function defaultScope()
{
return array('condition' => 'Active = 1');
}
in model:
'category' => array(self::BELONGS_TO, 'Category', 'CategoryID'),
'query':
$data = Article::model()->findAll();
MY SOLUTION
So i decided, that doing it in framework is:
inneficient
too much work
not good as it moves business logic away from database - this is fairly important to save work later on when working on interfaces/webservices and other customizations that should be part of the product.
Overall lesson: Try to keep all business logic as close to database as possible to prevent disrepancies.
First, i was thinking using triggers that would propagate soft delete down the hierarchy. However after thinking a bit more i decided not to do this. The reason is, that this way if I (or an interface or something) decided to reactivate the parent records, there would be no way to say which child record was chain-deleted and which one was deleted before:
CASE:
Lets say Category and Article.
First, one article is deleted.
Then the whole category is deleted.
Then you realize this was a mistake and you want to undelete the Category. How do you know which article was deleted by deleting category and which one should stay deleted? Yes there are solutions, ie timestamps but ...... too complex, too easy to break
So my solution in the end are:
VIEWS. I think i will move away from yii ORM to using views for anything more complex then basic things.
There are two advantages to this for me:
1) as a DBA i can do better SQL faster
2) logic stays in database, in case the application changes/another one is added, there is no need to implement the logic in more then one places
You need to specify condition when you are using findAll method. So You should use CDbCriteria for this purpose:
$criteria=new CDbCriteria;
$criteria->with = "category";
$criteria->condition = "category.Active = 1"; //OR $criteria->compare('category.active', 1 true);
$data = Article::model()->findAll($criteria);
You should also have a defaultScope in your Article model, condition there should add category.Active = 1 or whatever your relation is named.
public function defaultScope()
{
return array('condition' => 't.Active = 1 AND category.Active = 1');
}
I don't remember by now but it might be you have to specify the relation:
return array(
'with' => array("category" => array(
'condition'=> "t.Active = 1 AND category.Active = 1",
)
);

Yii Framework - two relations via the same "through" table

Mine goal is to have possibility to search Documents via the Users Names and Surnames and also via Recrutation Year and Semester.
Documents are related only to Declarations in such a way that Document are connected to exatly one Declaration and Declaration can be connected to exatly One or none Documents.
Declarations are related to OutgoingStudent and Recrutation.
So when quering Documents I want to query also OutgoingStudent and Recrutations via the Declaration table.
My code for relations in Documents:
return array(
'declaration' => array(self::BELONGS_TO, 'Declaration', 'DeclarationID'),
'outgoingStudentUserIdUser' => array(self::HAS_ONE, 'OutgoingStudent', 'OutgoingStudent_User_idUser','through'=>'declaration',),
'Recrutation' => array(self::HAS_ONE, 'Recrutation', 'Recrutation_RecrutationID','through'=>'declaration'),
);
And now when in search() function I want to make a query ->with
'declaration','outgoingStudentUserIdUser' and 'Recrutation':
$criteria->with = array('declaration','Recrutation','outgoingStudentUserIdUser');
I'm getting this error:
CDbCommand nie zdołał wykonać instrukcji SQL: SQLSTATE[42000] [1066]
Not unique table/alias: 'declaration'. The SQL statement executed was:
SELECT COUNT(DISTINCT t.DeclarationID) FROM Documents t LEFT
OUTER JOIN Declarations declaration ON
(t.DeclarationID=declaration.idDeclarations) LEFT OUTER JOIN
Recrutation Recrutation ON
(declaration.Recrutation_RecrutationID=Recrutation.RecrutationID)
LEFT OUTER JOIN Declarations declaration ON
(t.DeclarationID=declaration.idDeclarations) LEFT OUTER JOIN
OutgoingStudent outgoingStudentUserIdUser ON
(declaration.OutgoingStudent_User_idUser=outgoingStudentUserIdUser.User_idUser)
When using only $criteria->with = array('declaration','Recrutation') or $criteria->with = array('declaration','outgoingStudentUserIdUser') there is no error only when using both.
So probably it should be done in some other way, but how?
I have so many things to tell you! Here they are:
I find your relations function declaration pretty messy, and I'm not sure if it is doing what you want it to do (in case it worked). Here are my suggestions to re-declare it:
First of all, 'outgoingStudentUserIdUser' looks like a terrible name for a relation. In the end, the relation will be to instances of outgoingStudentUser, not only to 'ids'. So allow me to name it just as outgoingStudentUser. Now, this is my code:
'outgoingStudentUser' => array(self::HAS_ONE, 'OutgoingStudent', array('idDocuments'=>'idOutgoingStudent'),'through'=>'declaration',),
where 'idDocuments' is Documents' model primary key, and idOutgoingStudent is OutgoingStudent's model primary key.
The second relation could be corrected in a very similar way:
'Recrutation' => array(self::HAS_ONE, 'Recrutation', array('idDocuments'=>'idRecrutation'),'through'=>'declaration'),
where 'idDocuments' is Documents' model primary key, and idRecrutation is Recrutation's model primary key.
You can find that this is the correct declaration here: http://www.yiiframework.com/doc/guide/1.1/en/database.arr#through-on-self
But that's not all. I have more to tell you! What you're doing with your code is senseless. 'with' is used to force eager loading on related objects. In the following code:
$criteria->with = array('declaration','Recrutation','outgoingStudentUserIdUser');
you're just specifying in $criteria that when you retrieve in DB an instance of Documents using this $criteria, it will also fetch the models linked to that instance by the relations passed as parameters to 'with'. That's eager loading. It is used to reduce the number of queries to database. Is like saying: "go to DB and get me this instance of Documents, but once you're there, bring to me once per all the instances of other tables related to this object".
Well, that's what you're declaring, but certainly that's not what you want to do. How I know? Because that declaration is useless inside a search() function. As you may see here: http://www.yiiframework.com/doc/api/1.1/CDbCriteria/#with-detail , 'with' is only useful in some functions, and search() is not one of them. Inside search(), eager loading is pointless, senseless and useless.
So I see myself forced to ask you what are you trying to do? You say: "Mine goal is to have possibility to search Documents via the Users Names and Surnames and also via Recrutation Year and Semester", but what do you mean by "search Documents via the Users Names and..."? Do you want something like this: $user->documents, to return all the documents associated with $user? I hope you could be more specific about that, but perhaps in another, more to-the-point, question.
You can also try this:
return array(
'declaration' => array(self::BELONGS_TO, 'Declaration', 'DeclarationID'),
'outgoingStudentUserIdUser' => array(self::HAS_ONE, 'OutgoingStudent', 'OutgoingStudent_User_idUser','through'=>'declaration',),
'Recrutation' => array(self::HAS_ONE, 'Recrutation', '', 'on'=>'declaration.id=Recrutation.Recrutation_RecrutationID'),
);

adding hasMany association causes find() to not work well

OK, I am a little bit lost...
I am pretty new to PHP, and I am trying to use CakePHP for my web-site.
My DB is composed of two tables:
users with user_id, name columns
copies with copy_id, copy_name, user_id (as foreign key to users) columns.
and I have the matching CakePHP elements:
User and Copy as a model
UserController as controller
I don't use a view since I just send the json from the controller.
I have added hasMany relation between the user model and the copy model see below.
var $hasMany = array(
'Copy' => array(
'className' => 'Friendship',
'foreignKey' => 'user_id'
)
);
Without the association every find() query on the users table works well, but after adding the hasMany to the model, the same find() queries on the users stop working (print_r doesn't show anything), and every find() query I am applying on the Copy model
$copy = $this->User->Copy->find('all', array(
'condition' => array('Copy.user_id' => '2')
));
ignores the condition part and just return the whole data base.
How can I debug the code execution? When I add debug($var) nothing happens.
I'm not an expert, but you can start with the following tips:
Try to follow the CakePHP database naming conventions. You don't have to, but it's so much easier to let the automagic happen... Change the primary keys in your tabel to 'id', e.g. users.user_is --> users.id, copies.copy_id -->copies.id.
Define a view, just for the sake of debugging. Pass whatever info from model to view with $this->set('users', $users); and display that in a <pre></pre> block
If this is your first php and/or CakePHP attempt, make sure you do at least the blog tutorial
Make CakePHP generate (bake) a working set of model/view/controllers for users and copies and examine the resulting code
There's good documentation about find: the multifunctional workhorseof all model data-retrieval functions
I think the main problem is this:
'condition' => array('Copy.user_id' => '2')
It should be "conditions".
Also, stick to the naming conventions. Thankfully Cake lets you override pretty much all its assumed names, but it's easier to just do what they expect by default.
The primary keys should be all named id
The controller should be pluralised: UsersController
First off, try as much as possible to follow CakePHP convention.
var $hasMany = array(
'Copy' => array(
'className' => 'Friendship',
'foreignKey' => 'user_id'
)
);
Your association name is 'Copy' which is a different table and model then on your classname, you have 'Friendship'.
Why not
var $hasMany = array(
'Copy' => array('className'=>'Copy')
);
or
var $hasMany = array(
'Friendship' => array('className'=>'Friendship')
);
or
var $hasMany = array(
'Copy' => array('className'=>'Copy'),
'Friendship' => array('className'=>'Friendship')
);
Also, check typo errors like conditions instead of condition
Your table name might be the problem too. I had a table named "Class" and that gave cake fits. I changed it to something like Myclass and it worked. Class was a reserved word and Copy might be one too.

Using Multiple Tables in a Zend Model and returning a combined result set

Hi This is either a very specific or very generic quetion - I'm not sure, and I'm new to the Zend framework / oo generally. Please be patient if this is a stupid Q...
Anyway, I want to create a model which does something like:
Read all the itmes from a table 'gifts' into a row set
for each row in the table, read from a second table which shows how many have been bought, the append this as another "field" in the returned row
return the row set, with the number bought included.
Most of the simple Zend examples seem to only use one table in a model, but my reading seems to suggest that I should do most of the work there, rather than in the controller. If this is too generic a question, any example of a model that works with 2 tables and returns an array would be great!
thanks for your help in advance!
I assume second tables is something like "gift_order" or something.
In this case you need to specify tables relationships beetween "gift" and and "gift_order" via foreign keys and describe it in table class.
It will look like this
class GiftOrder extends Zend_Db_Table_Abstract
{
/** Table name */
protected $_name = 'gif_order';
protected $_referenceMap = array(
"Fileset" =>array(
"columns" => array("gifId"),
"refTableClass" => "Gift",
"refColumns" => array("id")
));
........................
You need to specify foreigh key constraint while create table with SQL
ALTER TABLE `gift_order`
ADD CONSTRAINT `order_to_gift` FOREIGN KEY (`giftId`) REFERENCES `gift` (`id`) ON DELETE CASCADE;
If this is something you looking for you could find more on this at this link link http://framework.zend.com/manual/en/zend.db.table.relationships.html
With such solution you will be able to loop gifts and get their orders without any complex SQL's
$rowSetGifts = $this->findGifts();
while($rowSetGifts->next()){
$gift = $rowSetGifts->current();
$orders = $gift->findGiftOrder();//This is magick methods, this is the same $gift->findDependentRowset('GiftOrder');
//Now you can do something with orders - count($orders), loop them or edit
}
I would recommend creating a function in your gifts model class that returns what you want. It would probably look something like:
public function getGiftWithAdditionalField($giftId) {
$select = $this->getAdapter()->select()
->from(array('g' => 'gifts'))
->joinLeft(array('table2' => 't2'), 'g.gift_id = t2.gift_id', array('field' => 'field'))
->where('g.gift_id = ?', $giftId);
return $this->getAdapter->fetchAll($select);
}
You can check out the Zend Framework Docs on Joins for more info.

Categories