Conditionally reuse a `hasOne` association multiple times inside the same query - cakephp

What I have
In my OrdersTable.php:
$this->hasOne('Total', [
'className' => 'App\Model\Table\TotalsTable',
'foreignKey' => 'order_id',
'propertyName' => 'Total'
]);
Actual totals table:
| id | order_id | type | value |
|----|----------|----------|-------|
| 1 | 1 | total | 100 |
| 2 | 1 | tax | 20 |
| 3 | 1 | shipping | 5 |
The structure and logic come from opencart/opencart and I have no control over that.
What I want
This is a non-functional concept:
$query = $ordersTable->find('all', array(
'contain' => array(
'TotalTax' => [
'associationName' => 'Total',
'conditions' => function($query) {
return $query->where([
'TotalTax.type' => 'tax',
]);
},
],
'TotalShipping' => [
'associationName' => 'Total',
'conditions' => function($query) {
return $query->where([
'TotalShipping.type' => 'shipping',
]);
},
],
),
));
Do you guys think something like this is possible?
UPD: Creating an association for each type isn't an option since there may be too many of them

If this functionality is something that will be reused with your codebase, I would implement this logic at the table level and have two different conditional associations:
In OrdersTable.php
$this->hasOne('TotalTax', [
'className' => 'Totals'
])
->setConditions(['TotalTax.type' => 'tax'])
->setDependent(true);
$this->hasOne('TotalShipping', [
'className' => 'Totals'
])
->setConditions(['TotalShipping.type' => 'shipping'])
->setDependent(true);
Then you can simply contain them in the query:
$query = $ordersTable->find()->contain(['TotalTax', 'TotalShipping'];
An example of this can be found in the CakePHP documentation

Related

Saving id of hasOne association

I have a table that looks like this:
,====,==============,============,==========,
| id | contact_from | contact_to | message |
|====|==============|============|==========|
| 1 | 1 | 2 | some msg |
| 2 | 2 | 1 | reply |
'----'--------------'------------'----------'
I create a new row, doing this:
public function add()
{
$message = $this->Messages->newEntity();
if ($this->request->is('post') && $this->request->is('ajax')) {
$data = $this->request->getData();
$data['contact_to'] = (int)$data['contact_to'];
$data['contact_from'] = (int)$this->Auth->user('id');
$message = $this->Messages->patchEntity($message, $data);
if ($this->Messages->save($message)) {
echo json_encode(['status' => 'success']);
exit;
}
echo json_encode(['status' => 'error']);
exit;
}
}
And this is my hasOne association:
$this->hasOne('ContactFrom', [
'className' => 'Contacts',
'foreignKey' => 'id',
'bindingKey' => 'contact_from',
'joinType' => 'INNER',
'propertyName' => 'contact_from'
]);
$this->hasOne('ContactTo', [
'className' => 'Contacts',
'foreignKey' => 'id',
'bindingKey' => 'contact_to',
'joinType' => 'INNER',
'propertyName' => 'contact_to'
]);
As you can see, I pass an ID to a new row, however it saves everything, except the id's. When I debug($message) after the patchEntity call, it comes back like this:
object(App\Model\Entity\Message) {
'message' => 'asdfasdf',
'date_sent' => object(Carbon\Carbon) {},
'[new]' => true,
'[accessible]' => [
'contact_to' => true,
'contact_from' => true,
'message' => true,
],
'[dirty]' => [
'message' => true,
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Messages'
}
It drops my ID's. I assume it's because I need to pass the Entity to it, but to save on db calls, how can I make it save the contact_to and contact_from id's to the table?
The names that you've choosen are causing a clash in the marshaller.
You cannot use the same name for the binding/foreign key and the property name, these two need to be different, as the former are ment to hold an identifier, and the latter is ment to hold either an entity, or an array that can be marshalled into an entity - neither of that applies to the value that you are passing, hence it will be discard.
You should ideally follow the CakePHP naming conventions, and append _id to your columns, ie name them contact_from_id and contact_to_id.
See also
Cookbook > CakePHP at a Glance > CakePHP Conventions > Database Conventions

Cakephp 3 Search plugin get related fields

I'm using the Easy model searching plugin from FriendsOfCake and use it to search into my table data. It's working very good but now I wan't to get related items by a foreign key that matched with the foreign key of the founded item.
On this moment my query is returning only the row by the content I search for, but how can I define that I also want the other rows with the same forKey?
My table looks like:
ID | forKey | name | content
------ | ------ | ------ | ------
1 | 1 | value1 | content1
2 | 1 | value2 | content2
3 | 2 | value3 | content3
Search function inside Controller:
$query = $this->Content
->find('search', [
'search' => $this->request->getQuery()
]);
Search setup inside Controller Table:
public function initialize(array $config) {
parent::initialize($config);
// Search
$this->addBehavior('Search.Search');
// Setup search filter using search manager
$this->searchManager()
// ->value('id')
->add('q', 'Search.Like', [
'before' => true,
'after' => true,
'fieldMode' => 'OR',
'comparison' => 'LIKE',
'wildcardAny' => '*',
'wildcardOne' => '?',
'field' => ['content'],
])
->add('foo', 'Search.Callback', [
'callback' => function ($query, $args, $filter) {
// Modify $query as required
}]);
}
I'm not sure if you're trying to find things that match your criteria in more than one field or if you're trying to find it in a related table.
For searching more fields from the calling table just add them comma separated:
'field' => ['content', 'id', 'name'],
If you are looking to bind the search to related items in other tables then include those in the controller search as contain:
$query = $this->Content
->find('search', [
'search' => $this->request->getQuery()
])
->contain(['OtherRelatedTable']);
Then add those to the model call:
'field' => ['content', 'id', 'name', 'OtherRelatedTable.id'],
Does that make sense?
If you are trying to filter using an associated model, use either the callback option or a custom finder:
$searchManager->add('related_table_field', 'Search.Callback', [
'callback' => function ($query, $args, $filter) {
$related_table_field = $args['related_table_field'];
$query->matching('RelatedTable', function ($q) use($related_table_field) {
return $q->where(['RelatedTable.related_table_field LIKE' => '%'.$related_table_field.'%']);
});
}
]);
$this->add('q', 'Search.Like', [
'before' => true,
'after' => true,
'fieldMode' => 'OR',
'comparison' => 'LIKE',
'wildcardAny' => '*',
'wildcardOne' => '?',
'field' => ['firstname', 'username']
])
->add('table_name.address', 'Search.Callback', [
'callback' => function ($query, $args, $filter) {
$query->matching('TableModel', function ($q) use($args) {
return $q->where(['TableModel.address' => $args['table_name.address']]);
});
return $query;
}
]);

How get data from Relationship tables

I have 2 table in my database that second(orders) table has foreign_key of primary key of first(books) table like these
Books
----+---------+-------------
id | slug | name |
----+---------+--------------
1 | math | mathematics |
----+---------+--------------
2 | physics | holidays |
-----------------------------
Orders
----+---------+-------+--------
id | book_id | count | price |
----+---------+-------+--------
1 | 2 | 12 | 100000 |
--------------------------------
I want result like below
result
----+---------+----------+----------+----------+------------------
id | book_id | slug | name | order_id | count | price |
----+---------+----------+----------+-----------------------------
1 | 2 | physics | holidays | 1 | 12 | 100000 |
------------------------------------------------------------------
I believe each product has a price. To that start with relating PRODUCT table to PRICE.
In cake this is done very easily:
in your Model (Product.php)
class Product extends AppModel{
public $hasMany = array('Price');
}
in Price.php
class Price extends AppModel{
public $hasBelongTo = array('Product');
}
in your controller if you query any of these Models the returned array will contain data from both models.
make the relationships as follow
Product.php
....
public $hasMany = array(
'Price' => array(
'className' => 'Price',
'foreignKey' => 'product_id',
'dependent' => true,
'conditions' => '',
'group' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
....
Price.php
.....
public $belongsTo = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
....
then in your controller use this query
$this->Product->find('all',array('contain'=>array('Price')));

Retrieving related model's data CakePHP

Here is the database tables in question:
Companies:
____________________
| id | name |
____________________
| 1| Unimex|
| 2| Solomex|
Users:
________________________
| id | name | company_id
_________________________
| 1| John | 1
| 2| Ricky| 2
Events:
_____________________________________
| id | user_id | details | date|
_____________________________________
| 1| 1| null | 2014-04-01
| 2| 1| null | 2014-04-15
| 3| 2| null | 2013-04-01
| 4| 1| null | 2014-04-02
What I would like to do is to retrieve a list of users(based on company's id) and their related events for a range of dates. What I have tried to do is the following:
$users = $this->User->find('all',
array(
'conditions' => array(
'company_id' => CakeSession::read("Auth.User.company_id")
),
'contain' => array(
'Event' => array(
'conditions' => array(
'Event.date >=' => $from,
'Event.date <=' => $to
)
)
)
)
);
This way I get the list of Users with their related events, but the $from and $to dates are not taken into consideration, meaning all of the events for a particular user are returned.
The relations are like following:
Event:
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
));
User:
var $hasMany = array(
'Event' => array(
'className' => 'Event',
'foreignKey' => 'user_id',
'dependent' => false,
)
);
var $belongsTo = array(
'Company' => array(
'className' => 'Company',
'foreignKey' => 'company_id',
'dependent' => false,
),
);
Company:
var $hasMany = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'company_id',
'dependent' => false
));
Any help or guidance is much appreciated.
Following are the two queries that are executed by cakePHP:
SELECT `User`.`id`, `User`.`company_id`, `User`.`name`, `User`.`surname`, `User`.`email`, `User`.`phone`, `Company`.`id`, `Company`.`name`, `Company`.`address` FROM `database`.`users` AS `User` LEFT JOIN `database`.`companies` AS `Company` ON (`User`.`company_id` = `Company`.`id`) WHERE `company_id` = 54
SELECT `Event`.`id`, `Event`.`customer_id`, `Event`.`user_id`,`Event`.`details`, `Event`.`hours`, `Event`.`minutes`, `Event`.`xhours`, `Event`.`xminutes`, `Event`.`assignment`, `Event`.`start_time` FROM `database`.`events` AS `Event` WHERE `Event`.`user_id` IN (124, 125, 126, 141, 143, 144, 147, 156)
So as you can see the date in the Event field conditions is not taken into consideration. If there is any other important information that I can provide, please, just let me know.
Moreover, I have just tried to retrieve only specific fields of the Event model, and that did not work as well. So basically the model is just contained as is, an no other parameter is applied.
Or maybe the model is not contained at all and the result I get is only because of the recursive being set to 1?
I don't see the error only as a suggestion
$users = $this->User->find('all',
array(
'contain' => array(
'Event' => array(
'conditions' => array(
'and' => array(
array(
'Event.date >=' => $from,
'Event.date <=' => $to
),
'company_id' => CakeSession::read("Auth.User.company_id")
)
)
)
)
)
);
The problem is that there was a typo. I always thought it has to be:
var $actAs = array('Containable');
But it actually had to be $actsAs.

Retrieving related model's data in CakePHP

Here is the database tables in question:
Companies:
____________________
| id | name |
____________________
| 1| Unimex|
| 2| Solomex|
Users:
________________________
| id | name | company_id
_________________________
| 1| John | 1
| 2| Ricky| 2
Events:
_____________________________________
| id | user_id | details | date|
_____________________________________
| 1| 1| null | 2014-04-01
| 2| 1| null | 2014-04-15
| 3| 2| null | 2013-04-01
What I would like to do is to retrieve events for a particular date based on company's id. What I have tried to do is the following:
$this->User->find('all',
array(
'conditions' => array(
'company_id' => CakeSession::read("Auth.User.company_id")
),
'contain' => array(
'Event' => array(
'conditions' => array(
'Event.date' => date("Y-m-d", $tomorrow)
)
)
)
));
but this retrieves all of the events for the company, the date condition is not being applied.
In the best case I would like to retrieve only the events for one company for a particular date. Otherwise I would get by with returning a list of users related to one company and their events for that particular date.
What would be the most efficient way to do this?
Any help or guidance is much appreciated.
Following are the relations between the tables:
Events:
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
));
Users:
var $belongsTo = array(
'Company' => array(
'className' => 'Company',
'foreignKey' => 'company_id',
'dependent' => false,
),
);
Here is the query that I get:
SELECT Event.id, Event.customer_id, Event.user_id, Event.project_id, Event.service_id, Event.date, Event.start_city, Event.material, Service.id, Service.company_id, Service.name, Service.service_nr, User.id, User.company_id, User.employee_nr, User.name, User.surname, User.email, User.password, User.role, Customer.id, Customer.company_id, Project.name, Project.description, Project.link_nr, Project.start_date, Project.finish_date, Project.project_nr FROM schedule.events AS Event LEFT JOIN schedule.services AS Service ON (Event.service_id = Service.id) LEFT JOIN schedule.users AS User ON (Event.user_id = User.id) LEFT JOIN schedule.customers AS Customer ON (Event.customer_id = Customer.id) LEFT JOIN schedule.projects AS Project ON (Event.Project_id = Project.id) WHERE 1 = 1
The problem right now is that the company id is not taken into concideration, and all of the events are being returned no matter what date it is.
I’ve tried this solution and it works for me.
I made all tables with small letters according to cake conventions like companies, events and users.
This is Event Model
class Event extends AppModel {
var $actsAs = array('Containable');
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
This is controller Event class
$tomorrow = '2013-04-01';
$this->Event->find('all',
array(
'conditions' => array(
'date' => date("Y-m-d", strtotime($tomorrow)),
),
'contain' => array(
'User' => array(
'conditions' => array(
'User.company_id' => CakeSession::read ('Auth.User.company_id')
)
)
)
));
This will give you
SELECT `Event`.`id`, `Event`.`user_id`, `Event`.`details`, `Event`.`date`, `User`.`id`, `User`.`name`, `User`.`company_id`
FROM `schedule`.`events` AS `Event`
LEFT JOIN `schedule`.`users` AS `User` ON (`Event`.`user_id` = `User`.`id` AND `User`.`company_id` = 2)
WHERE `date` = '2013-04-01'
I hope this will work for you. Thanks
try this code:
$this->Event->find('all',
array(
'conditions' => array(
'User.company_id' => CakeSession::read("Auth.User.company_id"),
'Event.date' => date("Y-m-d", $tomorrow)
),
'contain' => array('User')
));

Resources