Cakephp 3 Search plugin get related fields - cakephp

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;
}
]);

Related

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

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

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

Cannot associate user role with user

I have a list of existing user roles in database for example like this:
+----+-------+-----------+
| id | level | label |
+----+-------+-----------+
| 1 | 0 | admin |
| 2 | 1 | moderator |
| 3 | 2 | blogger |
+----+-------+-----------+
And I would like to attach a role to a user when its created. What I've tried is following:
In my BcUsersController:
$bcUser = $this->BcUsers->patchEntity($bcUser, $this->request->data,
[
'associated' => [
'BcUserInfos',
'BcUserRoles'
]
]
);
if ($this->BcUsers->save($bcUser))...
Associations:
//BcUserRolesTable
$this->belongsToMany('BcUsers', [
'className' => 'BcUsers',
'foreignKey' => 'role',
'propertyName' => 'BcUserRoles'
]);
//BcUsersTable
$this->hasMany('BcUserRoles', [
'className' => 'BcUserRoles',
'foreignKey' => 'id',
'bindingKey' => 'role',
'propertyName' => 'BcUserRoles',
'joinTable' => 'bc_user_roles'
]);
And this is how I try to inject userRoleId into a user table:
<?= $this->Form->input('BcUserRoles.level', ['type' => 'hidden', 'value' => '0']); ?>
or
<?= $this->Form->input('BcUserRoles.id', ['type' => 'hidden', 'value' => '1']); ?>
When I try to save it gives me no error just refreshes a page and with post data inside fields. If I remove all the associations code, it saves user + userInfo. What am I missing here?
EDIT
I just found an error in my $bcUser variable:
'[errors]' => [
'role' => [
'_required' => 'This field is required'
]
],
And I think thats why it does not save BcUser, but also userRole associations is empty:
'BcUserRoles' => [],
so it wouldnt save userRole to user table even if I would would remove
->requirePresence('role', 'create') from BcUsersTable, would it ?
Just tested. It throws me SQL error because role isnt in query parameters and in database it has to be there, null is not allowed. All users should have a role.
Looks like there are a couple issues here.
Associations
If BcUsersTable contains the foreign key for BcUserRoles then is it a belongsTo relationship.
belongsTo: the current model contains the foreign key.
http://book.cakephp.org/3.0/en/orm/associations.html#belongsto-associations
Equally this would also mean that BcUserRoles has a hasMany relationship to BcUsers not a belongsTo relationship.
hasMany: the other model contains the foreign key.
http://book.cakephp.org/3.0/en/orm/associations.html#hasmany-associations
Post data
I assume that the foreign key for BcUserRolesTable in the BcUsersTable is role. That means you will need to submit a value for role that is the id of the user role you wish to associate.
The following would create a form element to do that:
$this->Form->input('role', ['type' => 'hidden', 'value' => $useRoleId])
Where $userRoleId is the id of the role you wish to associate.
Try updating your belongsTo Relations
$this->belongsTo('BcUsers', [
'className' => 'BcUsers',
'foreignKey' => 'role',
'bindingKey' => 'id',
'propertyName' => 'BcUserRoles'
]);
make it
$this->belongsTo('BcUserRoles', [
'className' => 'BcUsers',
'foreignKey' => 'role',
'bindingKey' => 'id',
]);
Hope your problem will be solved

CakePHP 3 Sort by associated model with HasOne relationship

Order HasOne Suborder
Suborder BelongsTo Order
I need to sort Orders by a field in Suborders, but sorting by virtual fields appears to have been removed in Cake 3.x
In OrdersTable.php, I have
$this->hasOne('Suborder', [
'className' => 'Suborders',
'foreignKey' => 'order_id',
'strategy' => 'select',
'conditions' => function ($exp, $query) {
return $exp->add(['Suborder.id' => $query
->connection()
->newQuery()
->select(['SSO.id'])
->from(['SSO' => 'suborders'])
->where([
'Suborder.order_id = SSO.order_id',
'SSO.suborder_type_id in' => [1, 2, 3]
])
->order(['SSO.id' => 'DESC'])
->limit(1)]);
}
]);
In OrdersController.php, I have
$this->paginate = [
'limit' => 20,
'order' => ['id' => 'desc'],
'sortWhitelist' => [
'id',
'title',
'client_order',
'substatus',
'Workflows.order_status_id',
'Clients.name',
'ProductTypes.type',
'Suborder.due_date',
'Suborder.created',
],
];
$orders = $this->paginate($collection);
In index.ctp, I have
$this->Paginator->sort('Suborder.created', 'Order Placed'),
$this->Paginator->sort('Suborder.due_date'),
and the error I'm getting is Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Suborder.created' in 'order clause'. How do I get Cake to include the Suborder in the initial query for sorting and pagination?
Edit:
$collection = $this->Orders->find()
->contain([
'Clients',
'CurrentAssignment.Users',
'Workflows.OrderStatuses.Category',
'Workflows.OrderStatuses.Departments' => function ($q) use ($uID) {
return $this->Departments->find()->matching('Users', function ($q) use ($uID) {
return $q->where(['Users.id' => $uID]);
});
},
'ClientProducts.ProductTypes',
'Reviews' => function ($q) {
return $q->where(['review_type_id is not' => 6]);
},
'Reviews.ReviewTypes',
'PublicNotes',
'ActiveReview',
'Suborder',
'Suborder.SuborderTypes',
'Suborders.SuborderTypes',
]);
and $collection is modified with 150 lines of wheres, orWheres, and joins based on a number of conditions.
You have configured the assocaition to use the select strategy, which will use a separate query to retrieve the data (currently wrongly documented), hence you cannot reference it in the main query used for pagination.
So you have to use the default join strategy instead if you want to sort on it.
See also
Cookbook > Database Access & ORM > Associations - Linking Tables Together > HasOne Associations

Saving data in associated table

I am unable to save data in related tables
I have tables
ProductsTable
ProductsPricesTable
in ProductsTable table I have declared a relation like
public function initialize( array $config ) {
parent::initialize($config);
...
$this->hasMany('ProductsPrices');
}
and in ProductsController's add action I have something like
if( $this->request->is('post') ) {
$this->Products->patchEntity( $entity, $this->request->data, [
'associated' => ['ProductsPrices']
]);
if( $this->Products->save( $entity ) ) {
....
....
}
}
and the request->data looks like
[
'category_id' => '68',
'code' => '20',
'name' => 'Tagliatelle Bolognese',
'description' => 'mit Fleischsauce ',
'order' => '20',
'active' => '1',
'offered' => '0',
'productsprices' => [
(int) 0 => [
'size_id' => '80',
'price' => '7'
]
]
]
it saves only product in prodcts table, but in products_prices table is nothing saved. whose structure looks like
id | product_id | size_id | price | created | modified
Any idea?

Resources