Grouping a select list (optgroup) in CakePHP 3 - php

I am trying to make a list of grouped things in CakePHP 3, to create a grouped list of things in a select list in a form. I'm not sure if I am missing something or if I'm expecting too much of Cake and should be doing more myself.
I have a controller called Issues and a self-referencing column called RelatedIssues. Each Issue belongs to a System, and it's the systems I want the issues grouped by.
In my IssuesTable.php:
$this->belongsTo('RelatedIssues', [
'className' => 'Issues',
'foreignKey' => 'issue_id'
]);
$this->belongsTo('Systems', [
'foreignKey' => 'system_id',
'joinType' => 'INNER'
]);
...and in my IssuesController's edit method:
$relatedIssues = $this->Issues->RelatedIssues->find('list', [
'groupField' => 'system_id'
]);
When I get to the drop-down list, items are grouped by system_id as specified, but I cannot figure out how to get them grouped by the System's title field. Is this even possible, or do I have to write a nice nested foreach structure to do this myself?

should be (can'try it now):
$relatedIssues = $this->Issues->RelatedIssues->find('list', [
'groupField' => 'system.title'
])->contain('Systems');

Consider the following, is more clear:
$relatedIssues = $this->Issues->RelatedIssues->find('list', [
'contain' => ['Systems'],
'order' => [ 'Systems.title' => 'ASC', 'RelatedIssues.title' => 'ASC'],
'groupField' => function($entity) {
return $entity->system->title;
}
]);

Related

CakePHP associations: handle null id

I have this model:
Proforma
->hasMany('ItemProformas', ['foreignKey' => 'proforma_id']);
->belongsTo('Customers', ['foreignKey' => 'customer_id']);
->belongsTo('ProformaStates', ['foreignKey' => 'proforma_state_id']);
->hasMany('Invoices', ['foreignKey' => 'proforma_id']);
ItemProformas
->belongsTo('Proformas', ['foreignKey' => 'proforma_id', 'joinType' => 'INNER']);
->belongsTo('ItemDeliveryNotes', ['foreignKey' => 'item_delivery_note_id']);
ItemDeliveryNotes
->belongsTo('DeliveryNotes', ['foreignKey' => 'delivery_note_id', 'joinType' => 'INNER']);
->belongsTo('ItemOrders', ['foreignKey' => 'item_order_id']);
->belongsTo('ItemOrdersTypes', ['foreignKey' => 'item_orders_type_id']);
->belongsTo('Products', ['foreignKey' => 'product_id']);
Each ItemProforma may have one ItemDeliveryNotes, otherwise the foreign key will be null. Here my paginate call:
$this->paginate = [
'contain' => [
'Customers',
'ProformaStates',
'ItemProformas' => ['ItemDeliveryNotes' => ['DeliveryNotes']]
]
];
With this model, I get all the itemProforma that have item_delivery_note_id set. Instead I'm interesed to get them all, even if item_delivery_note_id is null.
I'm not sure if belongsTo is correct here (I mean in ItemProformas definition). But hasOne implies it has one associated row, not may have one.
What is the correct syntax to retrieve all itemProformas even if they don't have any ItemDeliveryNote associated? But if they have, I need to retrieve the ItemDeliveryNote object as well.
The association type depends on your schema. If the foreign key is in the source table, then it's belongsTo, if the foreign key is in the target table, then it's hasOne.
Whether a related record must exist primarily depends on the schema too, not on the type of association. If the foreign key is nullable, then the related record is optional. If and how you implement enforcing that constraint on application level is a different story.
That being said, ItemDeliveryNotes and DeliveryNotes are both belongsTo that will use joins by default, so both associations will be joined into the same query, and since you've configured the DeliveryNotes association to use an INNER join, it will exclude rows where no DeliveryNotes exist, which of course is also the case when no ItemDeliveryNotes exist.
Assuming your schema is modeled correctly/properly, you could for example change your association config to use a LEFT join by default in case applicable, or you could change the configuration for the containment on a per query basis (being it manually, or by using a custom finder):
$this->paginate = [
'contain' => [
'Customers',
'ProformaStates',
'ItemProformas' => [
'ItemDeliveryNotes' => [
'DeliveryNotes' => [
'joinType' => \Cake\Database\Query::JOIN_TYPE_LEFT,
],
],
],
],
];
Changing the fetching strategy for ItemDeliveryNotes could work too (though it might be quite taxing depending on the amount of records), ie using the select strategy instead of the join strategy, then the associated ItemDeliveryNotes records are being retrieved in a separate query, and thus won't affect retrieval of ItemProformas:
$this->paginate = [
'contain' => [
'Customers',
'ProformaStates',
'ItemProformas' => [
'ItemDeliveryNotes' => [
'strategy' => \Cake\ORM\Association::STRATEGY_SELECT,
'DeliveryNotes',
],
],
],
];

CakePHP 3: TranslateBehavior on a model that works as an alias

In a project I have two models, Products and Packages. Packages can be seen as containers of Products and to define the items in a package I've created a model PackageItem (which is basically a Product so its using the same table). Now Products (and so PackageItems) have translatable fields such as as a title and description.
ProductsTable.php contains:
$this->addBehavior('Translate', [
'fields' => ['title', 'description'],
'translationTable' => 'products_translations'
]);
$this->belongsToMany('PackageItems', [
'foreignKey' => 'package_id',
'joinType' => 'LEFT',
'joinTable'=>'products_package_items'
]);
PackageItemsTable contains:
$this->table('products');
$this->addBehavior('Translate', [
'fields' => ['title', 'description'],
'translationTable' => 'products_translations'
]);
$this->belongsTo('Products', [
'foreignKey' => 'package_item_id',
'joinType' => 'LEFT'
]);
Using TranslateBehavior I'm able return the translations on the Product but I can't figure out how to write the query I need to also return the translation on the PackageItems. This is my current query:
$package = $this->Products->find('translations')
->where(['business_id'=>$q['business_id'], 'id'=>$id, 'type'=>'Package'])
->contain([
'PackageItems'=>[
'Prices'=>function($q) {
return $q->where(['product_id'=>$this->product_id]);
}
]
])
->first();
You need two things
1) Set the proper reference name
The translate behavior on the PackageItemsTable class needs to be configured to use the same reference name (the value that is stored in the model column) as the behavior on the ProductsTable class, otherwise you'd never receive any translations, as it would by default look for PackageItems.
This is what the referenceName option can be used for. The reference name is being derived from the class name (not the alias), or for auto-tables, from the database table name or the alias. So for your ProductsTable class it would be Products.
Either set the name manually
$this->addBehavior('Translate', [
'fields' => ['title', 'description'],
'translationTable' => 'products_translations',
'referenceName' => 'Products' // there it goes
]);
or retrieve it dynamically from the behavior on the ProductsTable, like
$referenceName = $this->Products
->target()
->behaviors()
->get('Translate')
->config('referenceName');
This however would need to be done after adding the corresponding belongsTo association for the Products table!
2) Use the translations finder for the containment
You need to configure the PackageItems containment to use the translations finder, which is as simple as
contain([
'PackageItems' => [
'finder' => 'translations', // there you go
'Prices' => function ($q) {
return $q->where(['product_id' => $this->product_id]);
}
]
])
See also
API > \Cake\ORM\Behavior\TranslateBehavior::_referenceName()
API > \Cake\ORM\Behavior\TranslateBehavior::$_defaultConfig
API > \Cake\ORM\Query::contain()

PHP: Laravel how to create model in multiple one-many relationship

I have a relationship between three models in my laravel application, user model has many pages and pages has many keywords, all of which correspond to three tables on the database with same names. I want to create a user and pass along the users pages and keywords to the pages. I doubt I have any idea how to do it. What I did is below but it does not work, even when I add keywords to $fillable it throws an exception no column keywords on pages
what I did is:
$pages = [
'name' => 'mimi',
'link' => 'mimi',
'category' => 'trivial',
'keywords' => [
['name' => 'GGG'],
['name' => 'DDD']
]
];
$user->pages()->create($pages);
You need to create the page first, than add the keywords. (not sure if the syntax is correct)
$pages = [
'name' => 'mimi',
'link' => 'mimi',
'category' => 'trivial'
];
$keywords = ['GGG','DDD'];
$page = $user->pages()->create($pages);
foreach($keywords as $keyword) {
$page->keywords()->create(['name' => $keyword]);
}

Limit 'contain' to the first one (CakePHP 3)

I have to Tables: Users hasmany Memberhip. I would like to build a query to get ALL Users but EACH User should only contain the FIRST Membership (after ordering).
$users = $this->Users->find()->contain([
'Membership' => function ($q) {
return $q->order([
'year' => 'ASC'
])->limit(1);
},
'Countries',
'Board',
]);
Seems good so far. The problem is, that this query only gets a single Membership alltogether. So in the end of all Users that are beeing fetched, only one User has one Membership.
How do I get CakePHP to fetch ONE Membership for EACH User?
Thanks!
Sort key for hasOne option doesn't exist (see : CakePhp3 issue 5007)
Anyway I had the same problem and thanks to ndm : here his solution
I ended up using this:
$this->hasOne('FirstPhoto', [
'className' => 'Photos',
'foreignKey' => false,
'conditions' => function (\Cake\Database\Expression\QueryExpression $exp, \Cake\ORM\Query $query) {
$subquery = $query
->connection()
->newQuery()
->select(['Photos.id'])
->from(['Photos' => 'photos'])
->where(['Photos.report_id = Reports.id'])
->order(['Photos.id' => 'DESC'])
->limit(1);
return $exp->add(['FirstPhoto.id' => $subquery]);
}
]);
Reference: How to limit contained associations per record/group?
You can do this is by creating another association (Make sure you don't include a limit you don't need one as the hasOne association creates the limit):
$this->hasOne('FirstMembership', [
'className' => 'Memberships',
'foreignKey' => 'membership_id',
'strategy' => 'select',
'sort' => ['FirstMembership.created' => 'DESC'],
'conditions' => function ($e, $query) {
return [];
}]);
This works for limiting to 1 but if you want to limit to 10 there still seems to be no good solution.

CakePHP: Counter caches, but with a small twist

I've been using counter caches for a lot of my models. One question I have that I am not sure how to do.
I have a User model, and an Activity model. Activity has a column called type that can be things like, run, walk, etc.
I know I can easily create a counter cache for activity_count in the users table. But I want to have counter columns like run_count, walk_count that count activities with type ="run", or type = "walk", etc. but with still all the benefits of automatic updates to the counts.
Is there an easy way to do this? Thanks!
You can do this using COunterCache... the only thing is, you should define seperate belongsTo relationships for each activity. First let me say this isn't the best wat to solve this in practice, but just because you asked:
var $belongsTo = array(
'UserWalk' => array(
'counterCache' => true,
'foreignKey' => 'user_id',
'className' => 'User',
'conditions' => array('Activity.type' => 'walk'),
'counterScope' => array('Activity.type' => 'walk')
),
'UserRun' => array(
'counterCache' => true,
'foreignKey' => 'user_id',
'className' => 'User',
'conditions' => array('Activity.type' => 'run'),
'counterScope' => array('Activity.type' => 'run')
),
);
After that Cake will look for the Count field for each associated model. The fieldsnaming for the counterChache should now be something like user_run_count or userrun_count (I do not know the convention for this)
nope. You can count type ="run", or type = "walk", or just normal count, but only one of them. On the other hand, the code to update that isn't too bad. You can also add that logic in afterSave.

Categories