grouping contained models - cakephp

PresentationView hasMany SlideView
SlideView model has field duration.
The presentation_duration = sum of all SlideView.duration fields from slide views that belong to the presentation.
I want to get list (paged) of presentations with presentation_duration for each presentation.
So far I am trying like this:
$this->paginate['contain'] = array(
'PresentationView' => array(
'SlideView' => array(
'group' => 'SlideView.presentation_view_id',
// 'conditions' => array('group' => 'SlideView.presentation_view_id'),
)
)
);
The commented line is an option that I tried.
Both methods end up with some SQL errors:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'SlideView.group' in 'fieldlist'
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'group' in 'where clause'
It seems that contain doesn not even recognize group key - is it even possible to use grouping like this? If yes how I can do it?

You cannot pass 'group' to your 'contain'. To do what you're hoping to do, you'll need to use JOINs - see CakePHP Joining Tables.

Related

Standalone year inputs with CakePHP 2.x FormHelper

I am using CakePHP 2.10.20 for a legacy project that requires a field to store a year. So no days or months are needed, just years. I have tried to use the MySQL YEAR field type, as well as an INT(4).
I'm using the FormHelper to create a year input as following ...
echo $this->Form->input('User.test_year', [
'type' => 'date',
'dateFormat' => 'Y',
'minYear' => date('Y'),
'maxYear' => date('Y') + 1
]);
After saving the request data contains an array for the User.test_year field which looks like ...
array(
'User' => array(
'id' => '1',
'test_year' => array(
'year' => '2020'
)
)
)
CakePHP always uses the array format for dates. When I usually debug a full date field (so year + month + day) in the beforeValidate() callback, it is already converted to the proper string format. However, when I try to debug this year-only field in beforeValidate(), it is still formatted as the same array.
Therefore, upon saving, I am confronted with a PHP notice and a MySQL error ...
Array to string conversion [CORE/Cake/Model/Datasource/DboSource.php, line 2220]
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Array' in 'field list'
SQL Query: UPDATE `my_database`.`users` SET `id` = 1, `test_year` = Array, `modified` = '2020-04-14 23:10:32' WHERE `my_database`.`users`.`id` = '1'
Clearly the year in the array is not being converted into a proper string prior to being inserted into the database. I've also tried to use the FormHelper::year() method instead of the generic FormHelper::input(), but this does not change anything.
Really curious to learn what I'm doing wrong or overlooking, so thumbs up in advance to any CakePHP aficionados willing to point me in the right way.
Stay safe and thanks!

How to select a specific field additionally to a tables default fields?

I an looking to use a JOIN to select data from a table and a view in CakePHP like so :
$this->Annonces->find('all')
->where($arrFiltres)
->order($arrOrder)
->join([
'table' => 'annonces_suivis',
'alias' => 'AnnoncesSuivis',
'conditions' => [...],
]);
And would like to be able to select all the fields from the first table and som of the jointed table like so :
->select(['Annonces.*', 'AnnoncesSuivis.id']);
But this creates a faulty SQL query.
.* isn't supported by the ORM Query, it will convert this to
Annonces.* AS Annonces__*
which is invalid SQL. It would work with the lower level Database Query (Connection::newQuery()), which doesn't add aliases, however it won't return entities, so that's probably not what you want.
See Cookbook > Database Access & ORM > Database Basics > \Cake\Database\Connection::newQuery()
Pass a table object
As of CakePHP 3.1 you can pass table objects to Query::select(), which will cause all the fields of the table to be selected.
$this->Annonces
->find('all')
->select(['AnnoncesSuivis.id'])
->select($this->Annonces)
->join([
'table' => 'annonces_suivis',
'alias' => 'AnnoncesSuivis',
'conditions' => [ /* ... */ ],
])
->where($arrFiltres)
->order($arrOrder);
That way the AnnoncesSuivis.id field, and all fields of Annonces will be selected.
See Cookbook > Database Access & ORM > Query Builder > Selecting All Fields From a Table
Build the fields from the schema
That's what passing a table object will cause internally too, and it's also supported in CakePHP < 3.1.
$query = $this->Annonces->find('all');
$fields = $query->aliasFields(
$this->Annonces->schema()->columns(),
$this->Annonces->alias()
);
$query
->select(array_merge(['AnnoncesSuivis.id'], $fields))
->join([
'table' => 'annonces_suivis',
'alias' => 'AnnoncesSuivis',
'conditions' => [ /* ... */ ],
])
->where($arrFiltres)
->order($arrOrder);
This would also work for the fields option that can be passed to Table::find(), though you'd have to use a separate query object in that case, like
$fields = $this->Annonces->query()->aliasFields(
$this->Annonces->schema()->columns(),
$this->Annonces->alias()
);
$this->Annonces->find('all', [
'fields' => array_merge(['AnnoncesSuivis.id'], $fields)
// ...
]);
Use Query::autoFields()
In ealier CakePHP version, you could also make use of Query::autoFields(), which, when set to true, will automatically include the fields of the main table and possible containments.
See Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Passing Conditions to Contain
Auto selecting all fields is the default behavior until you set fields via Query::select(), in that case you'll have to explicitly enable Query::autoFields().
$this->Annonces
->find('all')
->select(['AnnoncesSuivis.id'])
->autoFields(true)
->join([
'table' => 'annonces_suivis',
'alias' => 'AnnoncesSuivis',
'conditions' => [ /* ... */ ],
])
->where($arrFiltres)
->order($arrOrder);
This should give you the desired query, however as mentioned this will only work for the main table and containments, if you'd wanted to include all fields of a manually joined table, then you'd have to specify them one by one.
You also can create virtual field in Entity:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class User extends Entity {
protected function _getFullName() {
return $this->_properties['first_name'] . ' ' . $this->_properties['last_name'];
}
}
echo $entity->full_name;

CakePHP 2.0 pagination and sum on hasMany-associated model

Project hasMany Comments
Payment (id, project_id, value, date)
I want to include in my index view a column equal to the sum of payments per project. I want to be able to sort by this field.
I've found some similiar problems here but neither of them concerns pagination.
My current (wrong) solution:
$this->paginate = array('Project' => array(
'conditions' => array('Project.archived =' => $archive),
'order' => 'Project.start_date DESC',
'contain' => array(
'Payment' => array(
'fields' => array('SUM(Payment.value) as Project__value_sum'),
'group' => array('Payment.project_id'),
)
)
));
$data = $this->paginate('Project');
$this->set('projects', $data);
$this->set('archive', $archive);
Seems like you'll probably want to do an afterSave on the Payment model.
Within the afterSave function, you run some MySQL that totals the payments (using MySQL SUM()) for the related Project, and store it in Project.payment_total.
Then, you can easily sort by that field, and it keeps you from having to run complex MySQL on every page, since it will only run when necessary (ie when there's a new or changed payment)

whats wrong with this query

I have this in my table model called Table
$test = $this->find('first', array(
'conditions' => array('table.test_id is NULL'),
'order'=> array('table.created ASC'),
)
);
it doesnt work. Tryingt to get the latest row with some criteria
Well, first of all, to get the latest row, you would want to organize by the created field descending, rather than ascending. Also, there are some problems with your syntax, that I have cleaned up below.
$this->find('first', array('conditions'=>array('Table.test_id'=>NULL), 'order'=>array('Table.created'=>'desc')));

CakePHP - HABTM - adding multiple tags to multiple points

I am trying to 'tag' multiple 'points' with multiple tags. I'm tagging my single points successfully. Unfortunately, when i try and use a tag, such as 'test2' on another point as a tag it is either giving me a duplicate entry error if i have my 'unique' set to false or if 'unique' is set to true, it will del my tag for all other points for 'test2' and create a single new one.
Here is what i have for my post data:
Array
(
[Tag] => Array
(
[id] => 4b7af6d7-787c-4f10-aa49-2502c0a80001
[name] => Test2
)
[Point] => Array
(
[id] => 4b47c66f-a130-4d12-8ccd-60824051e4b0
)
)
In my tag model i have this:
public $hasAndBelongsToMany = array(
'Point' => array(
'className' => 'Point',
'joinTable' => 'points_tags',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'point_id',
'unique' => false)
);
I have tried this with 'unique' set as true, too. Unfortunately, this will delete any other instances of 'Test2' in the join table ('points_tags').
I have tried this using both save() and saveAll(). Both are giving me this error:
Warning (512): SQL Error: 1062: Duplicate entry '4b7af6d7-787c-4f10-aa49-2502c0a80001-4b47c66f-a130-4d12-8ccd-608' for key 'MAN_ADD' [CORE/cake/libs/model/datasources/dbo_source.php, line 527]
Query: INSERT INTO points_tags (tag_id,point_id,id) VALUES ('4b7af6d7-787c-4f10-aa49-2502c0a80001','4b47c66f-a130-4d12-8ccd-60824051e4b0','4b7b39f3-46f8-4744-ac53-3973c0a80001')
Thoughts????
Suggestions????
Where does the id come from? I'm guessing its a primary key of the table, and from what I understand from your post (please write more clearly, help us help you) the problem isn't with points or tags, but with the id in the points_tags table.
When you use the save method, are you doing it inside of a loop? Remember, best practice is to call model::create() whenever you're saving in a loop.
I frequently find that when I have issues with the HABTM saving behavior, it's because I didn't call model::create.

Resources